Skip to content

fix: Resolve included files to their calling modules in IDE layer #17863

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/hir-def/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,14 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba

fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;

fn include_macro_invoc(&self, crate_id: CrateId) -> Vec<(MacroCallId, EditionedFileId)>;
fn include_macro_invoc(&self, crate_id: CrateId) -> Arc<[(MacroCallId, EditionedFileId)]>;
}

// return: macro call id and include file id
fn include_macro_invoc(
db: &dyn DefDatabase,
krate: CrateId,
) -> Vec<(MacroCallId, EditionedFileId)> {
) -> Arc<[(MacroCallId, EditionedFileId)]> {
db.crate_def_map(krate)
.modules
.values()
Expand Down
91 changes: 47 additions & 44 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,59 +770,62 @@ impl<'db> SemanticsImpl<'db> {
let file_id = self.find_file(&parent).file_id.file_id()?;

// iterate related crates and find all include! invocations that include_file_id matches
for (invoc, _) in self
for iter in self
.db
.relevant_crates(file_id.file_id())
.iter()
.flat_map(|krate| self.db.include_macro_invoc(*krate))
.filter(|&(_, include_file_id)| include_file_id == file_id)
.map(|krate| self.db.include_macro_invoc(*krate))
{
let macro_file = invoc.as_macro_file();
let expansion_info = {
self.with_ctx(|ctx| {
ctx.cache
.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
for (invoc, _) in
iter.iter().filter(|&&(_, include_file_id)| include_file_id == file_id)
{
let macro_file = invoc.as_macro_file();
let expansion_info = {
self.with_ctx(|ctx| {
ctx.cache
.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());

let InMacroFile { file_id, value } = exp_info.expanded();
if let InFile { file_id, value: Some(value) } = exp_info.arg() {
self.cache(value.ancestors().last().unwrap(), file_id);
}
self.cache(value, file_id.into());
let InMacroFile { file_id, value } = exp_info.expanded();
if let InFile { file_id, value: Some(value) } = exp_info.arg() {
self.cache(value.ancestors().last().unwrap(), file_id);
}
self.cache(value, file_id.into());

exp_info
})
.clone()
})
};
exp_info
})
.clone()
})
};

// FIXME: uncached parse
// Create the source analyzer for the macro call scope
let Some(sa) = expansion_info
.arg()
.value
.and_then(|it| self.analyze_no_infer(&it.ancestors().last().unwrap()))
else {
continue;
};
// FIXME: uncached parse
// Create the source analyzer for the macro call scope
let Some(sa) = expansion_info
.arg()
.value
.and_then(|it| self.analyze_no_infer(&it.ancestors().last().unwrap()))
else {
continue;
};

// get mapped token in the include! macro file
let span = span::Span {
range: token.text_range(),
anchor: span::SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
ctx: SyntaxContextId::ROOT,
};
let Some(InMacroFile { file_id, value: mut mapped_tokens }) =
expansion_info.map_range_down_exact(span)
else {
continue;
};
// get mapped token in the include! macro file
let span = span::Span {
range: token.text_range(),
anchor: span::SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
ctx: SyntaxContextId::ROOT,
};
let Some(InMacroFile { file_id, value: mut mapped_tokens }) =
expansion_info.map_range_down_exact(span)
else {
continue;
};

// if we find one, then return
if let Some(t) = mapped_tokens.next() {
return Some((sa, file_id.into(), t, span));
// if we find one, then return
if let Some(t) = mapped_tokens.next() {
return Some((sa, file_id.into(), t, span));
}
}
}

Expand Down
30 changes: 25 additions & 5 deletions crates/hir/src/semantics/source_to_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ use hir_def::{
},
hir::{BindingId, LabelId},
AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId,
FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId,
StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId,
FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId,
ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId,
VariantId,
};
use hir_expand::{
attrs::AttrId, name::AsName, ExpansionInfo, HirFileId, HirFileIdExt, MacroCallId,
Expand Down Expand Up @@ -131,11 +132,30 @@ impl SourceToDefCtx<'_, '_> {
for &crate_id in self.db.relevant_crates(file).iter() {
// Note: `mod` declarations in block modules cannot be supported here
let crate_def_map = self.db.crate_def_map(crate_id);
mods.extend(
let n_mods = mods.len();
let modules = |file| {
crate_def_map
.modules_for_file(file)
.map(|local_id| crate_def_map.module_id(local_id)),
)
.map(|local_id| crate_def_map.module_id(local_id))
};
mods.extend(modules(file));
if mods.len() == n_mods {
mods.extend(
self.db
.include_macro_invoc(crate_id)
.iter()
.filter(|&&(_, file_id)| file_id == file)
.flat_map(|(call, _)| {
modules(
call.lookup(self.db.upcast())
.kind
.file_id()
.original_file(self.db.upcast())
.file_id(),
)
}),
);
}
}
if mods.is_empty() {
// FIXME: detached file
Expand Down
6 changes: 1 addition & 5 deletions crates/ide-diagnostics/src/handlers/macro_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,7 @@ fn f() {

#[test]
fn include_does_not_break_diagnostics() {
let mut config = DiagnosticsConfig::test_sample();
config.disabled.insert("inactive-code".to_owned());
config.disabled.insert("unlinked-file".to_owned());
check_diagnostics_with_config(
config,
check_diagnostics(
r#"
//- minicore: include
//- /lib.rs crate:lib
Expand Down
12 changes: 12 additions & 0 deletions crates/ide-diagnostics/src/handlers/unlinked_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,18 @@ $0
mod bar {
mod foo;
}
"#,
);
}

#[test]
fn include_macro_works() {
check_diagnostics(
r#"
//- minicore: include
//- /main.rs
include!("bar/foo/mod.rs");
//- /bar/foo/mod.rs
"#,
);
}
Expand Down
6 changes: 6 additions & 0 deletions crates/ide-diagnostics/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
let mut actual = annotations.remove(&file_id).unwrap_or_default();
let expected = extract_annotations(&db.file_text(file_id));
actual.sort_by_key(|(range, _)| range.start());
// FIXME: We should panic on duplicates instead, but includes currently cause us to report
// diagnostics twice for the calling module when both files are queried.
actual.dedup();
// actual.iter().duplicates().for_each(|(range, msg)| {
// panic!("duplicate diagnostic at {:?}: {msg:?}", line_index.line_col(range.start()))
// });
if expected.is_empty() {
// makes minicore smoke test debuggable
for (e, _) in &actual {
Expand Down