Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

모호한/모호하지 않은 타입과 상수

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곳이 될 수 있습니다:

  1. 모호하지 않은 타입 위치에서 TyKind::Infer로
  2. 모호하지 않은 상수 인자 위치에서 ConstArgKind::Infer로
  3. 모호한 위치에서 GenericArg::Type(TyKind::Infer)로
  4. 모호한 위치에서 GenericArg::Const(ConstArgKind::Infer)로 표현되는 경우
  5. 모호한 위치에서 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 방문자 작성 과정에서 오류가 덜 발생하도록, 비교적 복잡한 시스템을 갖추고 있습니다:

  1. 컴파일러에는 타입이나 상수가 모호하지 않은 위치에 있을 때와 모호한 위치에 있을 때를 위한 서로 다른 타입인 Ty<AmbigArg>와 Ty<()>가 있습니다. AmbigArg는 거주 불가능한(uninhabited) 타입으로, 모호한 위치에 있을 때 TyKind와 ConstArgKind의 Infer 변형(variant)을 선택적으로 “비활성화“하는 데 사용합니다.

  2. HIR 방문자의 visit_ty 및 visit_const_arg 메서드는 타입/상수의 모호한 위치 버전만 받습니다. 모호하지 않은 타입/상수는 방문 과정에서 암묵적으로 모호한 타입/상수로 변환되며, Infer 변형은 전용 visit_infer 메서드에서 처리됩니다.

이 방식에는 여러 가지 이점이 있습니다:

  • GenericArg::Type/Const가 추론된 타입/상수 인자를 표현할 수 없다는 점이 명확합니다.
  • visit_ty와 visit_const_arg의 구현자는 추론된 타입/상수를 결코 마주치지 않으므로, 올바르게 동작하는 것처럼 보이지만 엣지 케이스를 잘못 처리하는 방문자를 작성하는 것이 불가능합니다.
  • visit_infer 메서드가 HIR에 있는 추론된 타입/상수의 모든 경우를 처리하므로, 방문자는 하나의 전용 위치에서 추론된 타입/상수를 쉽게 처리할 수 있고 경우를 빠뜨리지 않게 됩니다.