Skip to content

Commit b52df91

Browse files
Stop expanding UseTrees during ItemTree lowering
1 parent 5587d0a commit b52df91

File tree

12 files changed

+320
-188
lines changed

12 files changed

+320
-188
lines changed

crates/hir/src/lib.rs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -472,27 +472,13 @@ impl Module {
472472
});
473473
}
474474

475-
DefDiagnosticKind::UnresolvedImport { ast, index } => {
476-
let use_item = ast.to_node(db.upcast());
477-
let hygiene = Hygiene::new(db.upcast(), ast.file_id);
478-
let mut cur = 0;
479-
let mut tree = None;
480-
ModPath::expand_use_item(
481-
db.upcast(),
482-
InFile::new(ast.file_id, use_item),
483-
&hygiene,
484-
|_mod_path, use_tree, _is_glob, _alias| {
485-
if cur == *index {
486-
tree = Some(use_tree.clone());
487-
}
488-
489-
cur += 1;
490-
},
491-
);
475+
DefDiagnosticKind::UnresolvedImport { id, index } => {
476+
let file_id = id.file_id();
477+
let item_tree = id.item_tree(db.upcast());
478+
let import = &item_tree[id.value];
492479

493-
if let Some(tree) = tree {
494-
sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
495-
}
480+
let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
481+
sink.push(UnresolvedImport { file: file_id, node: AstPtr::new(&use_tree) });
496482
}
497483

498484
DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {

crates/hir_def/src/item_tree.rs

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -523,21 +523,38 @@ impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
523523
}
524524
}
525525

526-
/// A desugared `use` import.
527526
#[derive(Debug, Clone, Eq, PartialEq)]
528527
pub struct Import {
529-
pub path: Interned<ModPath>,
530-
pub alias: Option<ImportAlias>,
531528
pub visibility: RawVisibilityId,
532-
pub is_glob: bool,
533-
/// AST ID of the `use` item this import was derived from. Note that many `Import`s can map to
534-
/// the same `use` item.
535529
pub ast_id: FileAstId<ast::Use>,
536-
/// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`.
537-
///
538-
/// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting
539-
/// precise diagnostics.
540-
pub index: usize,
530+
pub use_tree: UseTree,
531+
}
532+
533+
#[derive(Debug, Clone, Eq, PartialEq)]
534+
pub struct UseTree {
535+
pub index: Idx<ast::UseTree>,
536+
kind: UseTreeKind,
537+
}
538+
539+
#[derive(Debug, Clone, Eq, PartialEq)]
540+
pub enum UseTreeKind {
541+
/// ```ignore
542+
/// use path::to::Item;
543+
/// use path::to::Item as Renamed;
544+
/// use path::to::Trait as _;
545+
/// ```
546+
Single { path: ModPath, alias: Option<ImportAlias> },
547+
548+
/// ```ignore
549+
/// use *; // (invalid, but can occur in nested tree)
550+
/// use path::*;
551+
/// ```
552+
Glob { path: Option<ModPath> },
553+
554+
/// ```ignore
555+
/// use prefix::{self, Item, ...};
556+
/// ```
557+
Prefixed { prefix: Option<ModPath>, list: Vec<UseTree> },
541558
}
542559

543560
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -711,6 +728,97 @@ pub struct MacroDef {
711728
pub ast_id: FileAstId<ast::MacroDef>,
712729
}
713730

731+
impl Import {
732+
/// Maps a `UseTree` contained in this import back to its AST node.
733+
pub fn use_tree_to_ast(
734+
&self,
735+
db: &dyn DefDatabase,
736+
file_id: HirFileId,
737+
index: Idx<ast::UseTree>,
738+
) -> ast::UseTree {
739+
// Re-lower the AST item and get the source map.
740+
// Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
741+
let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
742+
let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
743+
let hygiene = Hygiene::new(db.upcast(), file_id);
744+
let (_, source_map) =
745+
lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree");
746+
source_map[index].clone()
747+
}
748+
}
749+
750+
impl UseTree {
751+
/// Expands the `UseTree` into individually imported `ModPath`s.
752+
pub fn expand(
753+
&self,
754+
mut cb: impl FnMut(Idx<ast::UseTree>, ModPath, /* is_glob */ bool, Option<ImportAlias>),
755+
) {
756+
self.expand_impl(None, &mut cb)
757+
}
758+
759+
fn expand_impl(
760+
&self,
761+
prefix: Option<ModPath>,
762+
cb: &mut dyn FnMut(
763+
Idx<ast::UseTree>,
764+
ModPath,
765+
/* is_glob */ bool,
766+
Option<ImportAlias>,
767+
),
768+
) {
769+
fn concat_mod_paths(prefix: Option<ModPath>, path: &ModPath) -> Option<ModPath> {
770+
match (prefix, &path.kind) {
771+
(None, _) => Some(path.clone()),
772+
(Some(mut prefix), PathKind::Plain) => {
773+
for segment in path.segments() {
774+
prefix.push_segment(segment.clone());
775+
}
776+
Some(prefix)
777+
}
778+
(Some(prefix), PathKind::Super(0)) => {
779+
// `some::path::self` == `some::path`
780+
if path.segments().is_empty() {
781+
Some(prefix)
782+
} else {
783+
None
784+
}
785+
}
786+
(Some(_), _) => None,
787+
}
788+
}
789+
790+
match &self.kind {
791+
UseTreeKind::Single { path, alias } => {
792+
if let Some(path) = concat_mod_paths(prefix, path) {
793+
cb(self.index, path, false, alias.clone());
794+
}
795+
}
796+
UseTreeKind::Glob { path: Some(path) } => {
797+
if let Some(path) = concat_mod_paths(prefix, path) {
798+
cb(self.index, path, true, None);
799+
}
800+
}
801+
UseTreeKind::Glob { path: None } => {
802+
if let Some(prefix) = prefix {
803+
cb(self.index, prefix, true, None);
804+
}
805+
}
806+
UseTreeKind::Prefixed { prefix: additional_prefix, list } => {
807+
let prefix = match additional_prefix {
808+
Some(path) => match concat_mod_paths(prefix, path) {
809+
Some(path) => Some(path),
810+
None => return,
811+
},
812+
None => prefix,
813+
};
814+
for tree in list {
815+
tree.expand_impl(prefix.clone(), cb);
816+
}
817+
}
818+
}
819+
}
820+
}
821+
714822
macro_rules! impl_froms {
715823
($e:ident { $($v:ident ($t:ty)),* $(,)? }) => {
716824
$(

crates/hir_def/src/item_tree/lower.rs

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ pub(super) struct Ctx<'a> {
3535
db: &'a dyn DefDatabase,
3636
tree: ItemTree,
3737
hygiene: Hygiene,
38-
file: HirFileId,
3938
source_ast_id_map: Arc<AstIdMap>,
4039
body_ctx: crate::body::LowerCtx<'a>,
4140
forced_visibility: Option<RawVisibilityId>,
@@ -47,7 +46,6 @@ impl<'a> Ctx<'a> {
4746
db,
4847
tree: ItemTree::default(),
4948
hygiene,
50-
file,
5149
source_ast_id_map: db.ast_id_map(file),
5250
body_ctx: crate::body::LowerCtx::new(db, file),
5351
forced_visibility: None,
@@ -561,30 +559,13 @@ impl<'a> Ctx<'a> {
561559
Some(id(self.data().impls.alloc(res)))
562560
}
563561

564-
fn lower_use(&mut self, use_item: &ast::Use) -> Vec<FileItemTreeId<Import>> {
562+
fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> {
565563
let visibility = self.lower_visibility(use_item);
566564
let ast_id = self.source_ast_id_map.ast_id(use_item);
565+
let (use_tree, _) = lower_use_tree(self.db, &self.hygiene, use_item.use_tree()?)?;
567566

568-
// Every use item can expand to many `Import`s.
569-
let mut imports = Vec::new();
570-
let tree = self.tree.data_mut();
571-
ModPath::expand_use_item(
572-
self.db,
573-
InFile::new(self.file, use_item.clone()),
574-
&self.hygiene,
575-
|path, _use_tree, is_glob, alias| {
576-
imports.push(id(tree.imports.alloc(Import {
577-
path: Interned::new(path),
578-
alias,
579-
visibility,
580-
is_glob,
581-
ast_id,
582-
index: imports.len(),
583-
})));
584-
},
585-
);
586-
587-
imports
567+
let res = Import { visibility, ast_id, use_tree };
568+
Some(id(self.data().imports.alloc(res)))
588569
}
589570

590571
fn lower_extern_crate(
@@ -884,3 +865,76 @@ fn lower_abi(abi: ast::Abi) -> Interned<str> {
884865
}
885866
}
886867
}
868+
869+
struct UseTreeLowering<'a> {
870+
db: &'a dyn DefDatabase,
871+
hygiene: &'a Hygiene,
872+
mapping: Arena<ast::UseTree>,
873+
}
874+
875+
impl UseTreeLowering<'_> {
876+
fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> {
877+
if let Some(use_tree_list) = tree.use_tree_list() {
878+
let prefix = match tree.path() {
879+
// E.g. use something::{{{inner}}};
880+
None => None,
881+
// E.g. `use something::{inner}` (prefix is `None`, path is `something`)
882+
// or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
883+
Some(path) => {
884+
match ModPath::from_src(self.db, path, &self.hygiene) {
885+
Some(it) => Some(it),
886+
None => return None, // FIXME: report errors somewhere
887+
}
888+
}
889+
};
890+
891+
let list =
892+
use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect();
893+
894+
Some(self.use_tree(UseTreeKind::Prefixed { prefix, list }, tree))
895+
} else {
896+
let is_glob = tree.star_token().is_some();
897+
let path = match tree.path() {
898+
Some(path) => Some(ModPath::from_src(self.db, path, &self.hygiene)?),
899+
None => None,
900+
};
901+
let alias = tree.rename().map(|a| {
902+
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
903+
});
904+
if alias.is_some() && is_glob {
905+
return None;
906+
}
907+
908+
match (path, alias, is_glob) {
909+
(path, None, true) => {
910+
if path.is_none() {
911+
cov_mark::hit!(glob_enum_group);
912+
}
913+
Some(self.use_tree(UseTreeKind::Glob { path }, tree))
914+
}
915+
// Globs can't be renamed
916+
(_, Some(_), true) | (None, None, false) => None,
917+
// `bla::{ as Name}` is invalid
918+
(None, Some(_), false) => None,
919+
(Some(path), alias, false) => {
920+
Some(self.use_tree(UseTreeKind::Single { path, alias }, tree))
921+
}
922+
}
923+
}
924+
}
925+
926+
fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree {
927+
let index = self.mapping.alloc(ast);
928+
UseTree { index, kind }
929+
}
930+
}
931+
932+
pub(super) fn lower_use_tree(
933+
db: &dyn DefDatabase,
934+
hygiene: &Hygiene,
935+
tree: ast::UseTree,
936+
) -> Option<(UseTree, Arena<ast::UseTree>)> {
937+
let mut lowering = UseTreeLowering { db, hygiene, mapping: Arena::new() };
938+
let tree = lowering.lower_use_tree(tree)?;
939+
Some((tree, lowering.mapping))
940+
}

crates/hir_def/src/item_tree/pretty.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,46 @@ impl<'a> Printer<'a> {
163163
}
164164
}
165165

166+
fn print_use_tree(&mut self, use_tree: &UseTree) {
167+
match &use_tree.kind {
168+
UseTreeKind::Single { path, alias } => {
169+
w!(self, "{}", path);
170+
if let Some(alias) = alias {
171+
w!(self, " as {}", alias);
172+
}
173+
}
174+
UseTreeKind::Glob { path } => {
175+
if let Some(path) = path {
176+
w!(self, "{}::", path);
177+
}
178+
w!(self, "*");
179+
}
180+
UseTreeKind::Prefixed { prefix, list } => {
181+
if let Some(prefix) = prefix {
182+
w!(self, "{}::", prefix);
183+
}
184+
w!(self, "{{");
185+
for (i, tree) in list.iter().enumerate() {
186+
if i != 0 {
187+
w!(self, ", ");
188+
}
189+
self.print_use_tree(tree);
190+
}
191+
w!(self, "}}");
192+
}
193+
}
194+
}
195+
166196
fn print_mod_item(&mut self, item: ModItem) {
167197
self.print_attrs_of(item);
168198

169199
match item {
170200
ModItem::Import(it) => {
171-
let Import { visibility, path, is_glob, alias, ast_id: _, index } = &self.tree[it];
201+
let Import { visibility, use_tree, ast_id: _ } = &self.tree[it];
172202
self.print_visibility(*visibility);
173-
w!(self, "use {}", path);
174-
if *is_glob {
175-
w!(self, "::*");
176-
}
177-
if let Some(alias) = alias {
178-
w!(self, " as {}", alias);
179-
}
180-
wln!(self, "; // {}", index);
203+
w!(self, "use ");
204+
self.print_use_tree(use_tree);
205+
wln!(self, ";");
181206
}
182207
ModItem::ExternCrate(it) => {
183208
let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it];

0 commit comments

Comments
 (0)