모호한/모호하지 않은 타입과 상수
AST/HIR에서 타입 인자와 상수 인자는 모호한(ambig) 위치와 모호하지 않은(unambig) 위치라는 두 종류의 위치에 올 수 있습니다. 모호한 위치는 타입과 상수 중 어느 쪽으로 파싱해도 유효한 위치입니다. 모호하지 않은 위치는 한 종류로만 파싱하는 것이 유효한 위치입니다.
#![allow(unused)]
fn main() {
fn func<T, const N: usize>(arg: T) {
// ^ Unambig type position
let a: _ = arg;
// ^ Unambig type position
func::<T, N>(arg);
// ^ ^
// ^^^^ Ambig position
let _: [u8; 10];
// ^^ ^^ Unambig const position
// ^^ Unambig type position
}
}
모호한 위치에 있는 대부분의 타입/상수는 파싱 중에 타입인지 상수인지 구분할 수 있습니다. 이에 대한 예외는 경로와 추론된 제네릭 인자뿐입니다.
경로
#![allow(unused)]
fn main() {
struct Foo<const N: usize>;
fn foo<const N: usize>(_: Foo<N>) {}
}
파싱 시점에는 중괄호로 감싸지 않은 모든 제네릭 인자를 타입으로 파싱합니다(즉, 이들은 ast::GenericArg::Ty가 됩니다). 위 예제에서 이는 Foo에 대한 제네릭 인자를 ast::Ty::Path(N)를 감싸는 ast::GenericArg::Ty로 파싱한다는 뜻입니다.
그다음 이름 해석 중에는 다음과 같이 처리합니다:
- 제네릭 인자 위치에서 제네릭 인자가 없는 단일 세그먼트 경로를 만나면, 먼저 타입 네임스페이스에서 해석을 시도하고, 실패하면 값 네임스페이스에서 해석을 시도합니다.
- 그 밖의 모든 종류의 경로는 타입 네임스페이스에서만 해석을 시도합니다.
이것이 구현된 위치는 LateResolutionVisitor::visit_generic_arg를 참고하십시오.
마지막으로 AST 로워링 중에 타입 인자를 로워링하려 할 때, 먼저 그것이 Ty::Path인지, 그리고 값 네임스페이스의 무언가로 해석되었는지 확인합니다. 그렇다면 익명 상수를 만들고 타입 인자 대신 상수 인자로 로워링합니다.
이것이 구현된 위치는 LoweringContext::lower_generic_arg를 참고하십시오.
경로에 대한 모호성은 HIR로 전파되지 않는다는 점에 유의하십시오. HIR ty 로워링 중에 Ty 또는 Const로 변환되는 hir::GenericArg::Path 같은 것은 존재하지 않습니다(그렇게 구현할 수도 있기는 합니다).
추론된 인자(_)
#![allow(unused)]
fn main() {
struct Foo<const N: usize>;
fn foo() {
let _unused: Foo<_>;
}
}
로워링 이후에도 모호하게 남는 제네릭 인자는 경로 세그먼트 안의 추론된 제네릭 인자(_)뿐입니다. 위 예제에서 파싱 시점에는 Foo에 대한 _ 인자가 추론된 타입 인자인지 추론된 상수 인자인지 분명하지 않습니다.
모호한 AST 위치에서 추론된 인자는 ast::Ty::Infer를 감싸는 ast::GenericArg::Ty로 파싱됩니다. 그다음 AST 로워링 중에 ast::GenericArg::Ty를 로워링할 때, 그것이 추론된 타입인지 확인하고, 그렇다면 hir::GenericArg::Infer로 로워링합니다.
모호하지 않은 AST 위치에서 추론된 인자는 ast::Ty::Infer 또는 ast::AnonConst로 파싱됩니다. AnonConst의 경우는 꽤 특이합니다. “익명 상수“의 “본문“을 나타내기 위해 ast::ExprKind::Underscore를 사용하지만, 실제로는 HIR에서 이를 익명 상수로 로워링하지 않습니다.
AST가 ast::GenericArg::Infer를 갖도록 리팩터링하여 AnonConst의 이러한 중복된 의미와 모호한 위치에서의 ast::Ty::Infer 재사용을 없앨 수 있는지 검토해 볼 가치가 있습니다.
모호하지 않은 AST 위치에서는 AST 로워링 중에 추론된 인자를 타입 위치인지 상수 위치인지에 따라 각각 hir::TyKind::Infer 또는 hir::ConstArgKind::Infer로 로워링합니다. 모호한 AST 위치에서는 AST 로워링 중에 추론된 인자를 hir::GenericArg::Infer로 로워링합니다. 이것이 구현된 위치는 LoweringContext::lower_generic_arg를 참고하십시오.
이를 단순하게 구현하면, HIR의 구조만 보고 추론된 타입/상수가 HIR에 있을 수 있다고 생각할 만한 위치가 최대 5곳이 될 수 있습니다:
- 모호하지 않은 타입 위치에서
TyKind::Infer로 - 모호하지 않은 상수 인자 위치에서
ConstArgKind::Infer로 - 모호한 위치에서
GenericArg::Type(TyKind::Infer)로 - 모호한 위치에서
GenericArg::Const(ConstArgKind::Infer)로 표현되는 경우 - 모호한 위치에서
GenericArg::Infer로 표현되는 경우
제네릭 인자 위치에서는 항상 GenericArg::Infer로 하강(lower)하므로 3번과 4번 위치는 실제로 마주칠 수 없다는 점에 유의하십시오.
이 방식에는 몇 가지 실패 유형이 있습니다:
GenericArg::Infer는 검사하지만hir::TyKind/ConstArgKind::Infer검사를 잊은 방문자(visitor)를 작성하여, 의도치 않게 모호한 위치의 추론만 처리하게 될 수 있습니다.TyKind/ConstArgKind::Infer는 검사하지만GenericArg::Infer검사를 잊은 방문자를 작성하여, 의도치 않게 모호하지 않은 위치의 추론만 처리하게 될 수 있습니다.- 모호한 위치의 추론된 타입/상수를
GenericArg::Type/Const로 표현하는 일이 결코 없다는 사실을 모른 채,GenericArg::Type/Const(TyKind/ConstArgKind::Infer)와GenericArg::Infer를 검사하는 방문자를 작성할 수 있습니다. - 추론된 상수 인자도 존재한다는 사실을 잊고
ConstArgKind::Infer는 검사하지 않은 채 오직TyKind::Infer만 검사하는 방문자를 작성할 수 있습니다(그 반대의 경우도 마찬가지입니다).
추론된 타입/상수를 다룰 때 HIR 방문자 작성 과정에서 오류가 덜 발생하도록, 비교적 복잡한 시스템을 갖추고 있습니다:
-
컴파일러에는 타입이나 상수가 모호하지 않은 위치에 있을 때와 모호한 위치에 있을 때를 위한 서로 다른 타입인
Ty<AmbigArg>와Ty<()>가 있습니다.AmbigArg는 거주 불가능한(uninhabited) 타입으로, 모호한 위치에 있을 때TyKind와ConstArgKind의Infer변형(variant)을 선택적으로 “비활성화“하는 데 사용합니다. -
HIR 방문자의
visit_ty및visit_const_arg메서드는 타입/상수의 모호한 위치 버전만 받습니다. 모호하지 않은 타입/상수는 방문 과정에서 암묵적으로 모호한 타입/상수로 변환되며,Infer변형은 전용visit_infer메서드에서 처리됩니다.
이 방식에는 여러 가지 이점이 있습니다:
GenericArg::Type/Const가 추론된 타입/상수 인자를 표현할 수 없다는 점이 명확합니다.visit_ty와visit_const_arg의 구현자는 추론된 타입/상수를 결코 마주치지 않으므로, 올바르게 동작하는 것처럼 보이지만 엣지 케이스를 잘못 처리하는 방문자를 작성하는 것이 불가능합니다.visit_infer메서드가 HIR에 있는 추론된 타입/상수의 모든 경우를 처리하므로, 방문자는 하나의 전용 위치에서 추론된 타입/상수를 쉽게 처리할 수 있고 경우를 빠뜨리지 않게 됩니다.