AST 로워링
AST 로워링 단계는 AST를 HIR로 변환합니다. 즉, 타입 분석이나 이와 유사한 구문 독립적 분석과 관련이 없는 많은 구조가 제거됩니다. 이러한 구조의 예는 다음과 같으며, 이에 국한되지 않습니다.
- 괄호
- 대체 없이 제거되며, 트리 구조가 순서를 명시적으로 나타냅니다.
for루프match+loop+match로 변환됩니다.
- 전칭
impl Trait- 제네릭 인자로 변환됩니다(단, 사용자가 직접 작성하지 않았음을 알 수 있도록 몇 가지 플래그가 붙습니다).
- 존재
impl Trait- 가상의
existential type선언으로 변환됩니다.
- 가상의
AST 로워링의 구현은 rustc_ast_lowering 크레이트에 있습니다. 진입점은 lower_to_hir이며, 이 함수는 TyCtxt에서 확장 이후의 AST와 리졸버 데이터를 가져와 크레이트 전체에 대한 hir::Crate를 빌드합니다.
로워링은 HIR 소유자를 중심으로 구성됩니다. lower_to_hir가 먼저 크레이트를 인덱싱한 다음, ItemLowerer::lower_node가 각 크레이트, 항목, 연관 항목, 외부 항목을 로워링합니다.
로워링 로직의 대부분은 LoweringContext에 있습니다. 구현은 rustc_ast_lowering 크레이트 안의 item.rs, expr.rs, pat.rs, path.rs 등 여러 파일로 나뉘어 있지만, 모두 동일한 LoweringContext 상태와 ID 로워링 메커니즘을 공유합니다.
각 소유자는 자신만의 with_hir_id_owner 스코프 안에서 로워링됩니다. 이 때문에 아래의 HirId 불변 조건이 중요합니다. lower_node_id는 AST NodeId를 현재 소유자에 매핑하고, next_id는 디슈가링 과정에서 도입되는 HIR 전용 노드를 새로 만듭니다.
로워링은 compiler/rustc_passes/src/hir_id_validator.rs의 정합성 검사에 걸리지 않도록 몇 가지 불변 조건을 지켜야 합니다.
HirId를 생성했다면 반드시 사용해야 합니다. 따라서lower_node_id를 사용했다면, 그 결과로 나온NodeId나HirId를 반드시 사용해야 합니다(HIR에 있는 모든NodeId는 대응하는HirId가 존재하는지 검사되므로 둘 중 어느 것을 사용해도 됩니다).HirId의 로워링은 그것을 소유하는 아이템의 스코프 안에서 이루어져야 합니다. 즉, 현재 로워링 중인 아이템이 아닌 다른 아이템의 일부를 생성하는 경우에는with_hir_id_owner를 사용해야 합니다. 예를 들어 존재impl Trait를 로워링할 때 이런 상황이 발생합니다.- HIR 구조에 들어갈
NodeId는 그HirId가 사용되지 않더라도 반드시 로워링해야 합니다.let _ = self.lower_node_id(node_id);를 호출하는 것은 전적으로 올바른 방법입니다. AST에 존재하지 않던 새 노드를 생성하는 경우에는 그 노드들을 위한 새 ID를 반드시 생성해야 합니다. 이는next_id메서드를 호출하여 수행하며, 이 메서드는 새NodeId를 생성하는 동시에 자동으로 로워링까지 해 주므로HirId도 함께 얻을 수 있습니다.
새 DefId를 생성하는 경우, 각 DefId에는 대응하는 NodeId가 있어야 하므로 로워링 중에 새 NodeId를 생성하지 않아도 되도록 이러한 NodeId를 AST에 추가해 두는 것이 좋습니다. 이렇게 하면 어떤 대상의 NodeId를 통해 그 DefId를 찾을 수 있는 방법이 생긴다는 이점이 있습니다. 로워링에서 이 DefId가 여러 곳에 필요한 경우, 그 모든 곳에서 새 NodeId를 생성할 수는 없습니다. 그렇게 하면 새로운 DefId도 함께 생기기 때문입니다. AST에서 가져온 NodeId를 사용하면 이 문제가 발생하지 않습니다.
또한 NodeId가 있으면 로워링이 그때그때 DefId를 생성하는 대신 DefCollector가 DefId를 생성할 수 있습니다. DefId 생성을 한곳에 모으면 리팩터링하기도 쉽고 코드를 이해하기도 쉬워집니다.