Skip to content

Commit 3dbf138

Browse files
committed
feat: Virtual macro files
1 parent ca47cdd commit 3dbf138

File tree

97 files changed

+1738
-1109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1738
-1109
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir-expand/src/files.rs

+42-5
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,45 @@ pub type HirFilePosition = FilePositionWrapper<HirFileId>;
3838
pub type MacroFilePosition = FilePositionWrapper<MacroFileId>;
3939
pub type FilePosition = FilePositionWrapper<EditionedFileId>;
4040

41-
impl From<FilePositionWrapper<EditionedFileId>> for FilePositionWrapper<span::FileId> {
42-
fn from(value: FilePositionWrapper<EditionedFileId>) -> Self {
41+
impl From<FilePosition> for FilePositionWrapper<span::FileId> {
42+
fn from(value: FilePosition) -> Self {
4343
FilePositionWrapper { file_id: value.file_id.into(), offset: value.offset }
4444
}
4545
}
46+
47+
impl From<FileRange> for HirFileRange {
48+
fn from(value: FileRange) -> Self {
49+
HirFileRange { file_id: value.file_id.into(), range: value.range }
50+
}
51+
}
52+
53+
impl From<FilePosition> for HirFilePosition {
54+
fn from(value: FilePosition) -> Self {
55+
HirFilePosition { file_id: value.file_id.into(), offset: value.offset }
56+
}
57+
}
58+
59+
impl FilePositionWrapper<span::FileId> {
60+
pub fn with_edition(self, edition: span::Edition) -> FilePosition {
61+
FilePositionWrapper {
62+
file_id: EditionedFileId::new(self.file_id, edition),
63+
offset: self.offset,
64+
}
65+
}
66+
}
67+
68+
impl FileRangeWrapper<span::FileId> {
69+
pub fn with_edition(self, edition: span::Edition) -> FileRange {
70+
FileRangeWrapper { file_id: EditionedFileId::new(self.file_id, edition), range: self.range }
71+
}
72+
}
73+
74+
impl<T> InFileWrapper<span::FileId, T> {
75+
pub fn with_edition(self, edition: span::Edition) -> InRealFile<T> {
76+
InRealFile { file_id: EditionedFileId::new(self.file_id, edition), value: self.value }
77+
}
78+
}
79+
4680
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
4781
pub struct FileRangeWrapper<FileKind> {
4882
pub file_id: FileKind,
@@ -191,6 +225,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
191225
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
192226
self.with_value(self.value.syntax())
193227
}
228+
pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
229+
FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
230+
}
194231
}
195232

196233
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
@@ -201,9 +238,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
201238
}
202239

203240
// region:specific impls
204-
impl<SN: Borrow<SyntaxNode>> InRealFile<SN> {
205-
pub fn file_range(&self) -> FileRange {
206-
FileRange { file_id: self.file_id, range: self.value.borrow().text_range() }
241+
impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
242+
pub fn file_range(&self) -> FileRangeWrapper<FileId> {
243+
FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
207244
}
208245
}
209246

crates/hir-expand/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ pub trait HirFileIdExt {
347347

348348
/// If this is a macro call, returns the syntax node of the very first macro call this file resides in.
349349
fn original_call_node(self, db: &dyn ExpandDatabase) -> Option<InRealFile<SyntaxNode>>;
350+
fn call_node(self, db: &dyn ExpandDatabase) -> Option<InFile<SyntaxNode>>;
350351

351352
fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option<InFile<ast::Attr>>;
352353
}
@@ -405,6 +406,10 @@ impl HirFileIdExt for HirFileId {
405406
}
406407
}
407408

409+
fn call_node(self, db: &dyn ExpandDatabase) -> Option<InFile<SyntaxNode>> {
410+
Some(db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).to_node(db))
411+
}
412+
408413
fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option<InFile<ast::Attr>> {
409414
let macro_file = self.macro_file()?;
410415
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);

crates/hir-expand/src/prettify_macro_expansion_.rs

+43-39
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,47 @@ pub fn prettify_macro_expansion(
2121
let crate_graph = db.crate_graph();
2222
let target_crate = &crate_graph[target_crate_id];
2323
let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
24-
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| {
25-
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
26-
let replacement =
27-
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
28-
let ctx_data = db.lookup_intern_syntax_context(ctx);
29-
let macro_call_id =
30-
ctx_data.outer_expn.expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
31-
let macro_call = db.lookup_intern_macro_call(macro_call_id);
32-
let macro_def_crate = macro_call.def.krate;
33-
// First, if this is the same crate as the macro, nothing will work but `crate`.
34-
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
35-
// will work in inserted code and match the user's expectation.
36-
// If not, the crate's display name is what the dependency name is likely to be once such dependency
37-
// is inserted, and also understandable to the user.
38-
// Lastly, if nothing else found, resort to leaving `$crate`.
39-
if target_crate_id == macro_def_crate {
40-
make::tokens::crate_kw()
41-
} else if let Some(dep) =
42-
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
43-
{
44-
make::tokens::ident(dep.name.as_str())
45-
} else if let Some(crate_name) = &crate_graph[macro_def_crate].display_name {
46-
make::tokens::ident(crate_name.crate_name().as_str())
47-
} else {
48-
return dollar_crate.clone();
49-
}
50-
});
51-
if replacement.text() == "$crate" {
52-
// The parent may have many children, and looking for the token may yield incorrect results.
53-
return dollar_crate.clone();
54-
}
55-
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
56-
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
57-
parent
58-
.children_with_tokens()
59-
.filter_map(NodeOrToken::into_token)
60-
.find(|it| it.kind() == replacement.kind())
61-
.unwrap()
62-
})
24+
syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
25+
syn,
26+
&mut |dollar_crate| {
27+
let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx;
28+
let replacement =
29+
syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
30+
let ctx_data = db.lookup_intern_syntax_context(ctx);
31+
let macro_call_id = ctx_data
32+
.outer_expn
33+
.expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
34+
let macro_call = db.lookup_intern_macro_call(macro_call_id);
35+
let macro_def_crate = macro_call.def.krate;
36+
// First, if this is the same crate as the macro, nothing will work but `crate`.
37+
// If not, if the target trait has the macro's crate as a dependency, using the dependency name
38+
// will work in inserted code and match the user's expectation.
39+
// If not, the crate's display name is what the dependency name is likely to be once such dependency
40+
// is inserted, and also understandable to the user.
41+
// Lastly, if nothing else found, resort to leaving `$crate`.
42+
if target_crate_id == macro_def_crate {
43+
make::tokens::crate_kw()
44+
} else if let Some(dep) =
45+
target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
46+
{
47+
make::tokens::ident(dep.name.as_str())
48+
} else if let Some(crate_name) = &crate_graph[macro_def_crate].display_name {
49+
make::tokens::ident(crate_name.crate_name().as_str())
50+
} else {
51+
return dollar_crate.clone();
52+
}
53+
});
54+
if replacement.text() == "$crate" {
55+
// The parent may have many children, and looking for the token may yield incorrect results.
56+
return None;
57+
}
58+
// We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
59+
let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
60+
parent
61+
.children_with_tokens()
62+
.filter_map(NodeOrToken::into_token)
63+
.find(|it| it.kind() == replacement.kind())
64+
},
65+
|_| (),
66+
)
6367
}

crates/hir/src/semantics.rs

+100-8
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ use hir_expand::{
2424
attrs::collect_attrs,
2525
builtin::{BuiltinFnLikeExpander, EagerExpander},
2626
db::ExpandDatabase,
27-
files::InRealFile,
27+
files::{HirFileRange, InRealFile},
2828
hygiene::SyntaxContextExt as _,
2929
inert_attr_macro::find_builtin_attr_idx,
3030
name::AsName,
31-
ExpandResult, FileRange, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
31+
ExpandResult, FileRange, HirFileIdExt, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
3232
};
3333
use intern::{sym, Symbol};
3434
use itertools::Itertools;
@@ -239,10 +239,21 @@ impl<DB: HirDatabase> Semantics<'_, DB> {
239239
self.imp.file_to_module_defs(file.into()).next()
240240
}
241241

242+
pub fn hir_file_to_module_def(&self, file: impl Into<HirFileId>) -> Option<Module> {
243+
self.imp.hir_file_to_module_defs(file.into()).next()
244+
}
245+
242246
pub fn file_to_module_defs(&self, file: impl Into<FileId>) -> impl Iterator<Item = Module> {
243247
self.imp.file_to_module_defs(file.into())
244248
}
245249

250+
pub fn hir_file_to_module_defs(
251+
&self,
252+
file: impl Into<HirFileId>,
253+
) -> impl Iterator<Item = Module> {
254+
self.imp.hir_file_to_module_defs(file.into())
255+
}
256+
246257
pub fn to_adt_def(&self, a: &ast::Adt) -> Option<Adt> {
247258
self.imp.to_def(a)
248259
}
@@ -327,6 +338,14 @@ impl<'db> SemanticsImpl<'db> {
327338
tree
328339
}
329340

341+
pub fn adjust_edition(&self, file_id: HirFileId) -> HirFileId {
342+
if let Some(editioned_file_id) = file_id.file_id() {
343+
self.attach_first_edition(editioned_file_id.file_id()).map_or(file_id, Into::into)
344+
} else {
345+
file_id
346+
}
347+
}
348+
330349
pub fn find_parent_file(&self, file_id: HirFileId) -> Option<InFile<SyntaxNode>> {
331350
match file_id.repr() {
332351
HirFileIdRepr::FileId(file_id) => {
@@ -697,6 +716,28 @@ impl<'db> SemanticsImpl<'db> {
697716
})
698717
}
699718

719+
/// Retrieves the formatting part of the format_args! template string at the given offset.
720+
pub fn check_for_format_args_template_with_file(
721+
&self,
722+
original_token: SyntaxToken,
723+
offset: TextSize,
724+
) -> Option<(HirFileRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
725+
let string_start = original_token.text_range().start();
726+
let original_token = self.wrap_token_infile(original_token);
727+
self.descend_into_macros_breakable2(original_token, |token, _| {
728+
(|| {
729+
self.resolve_offset_in_format_args(
730+
ast::String::cast(token.value)?,
731+
offset.checked_sub(string_start)?,
732+
)
733+
.map(|(range, res)| {
734+
(HirFileRange { file_id: token.file_id, range: range + string_start }, res)
735+
})
736+
})()
737+
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
738+
})
739+
}
740+
700741
fn resolve_offset_in_format_args(
701742
&self,
702743
string: ast::String,
@@ -891,6 +932,14 @@ impl<'db> SemanticsImpl<'db> {
891932
self.descend_into_macros_impl(token.clone(), &mut cb)
892933
}
893934

935+
pub fn descend_into_macros_breakable2<T>(
936+
&self,
937+
token: InFile<SyntaxToken>,
938+
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>,
939+
) -> Option<T> {
940+
self.descend_into_macros_impl_(token.clone(), &mut cb)
941+
}
942+
894943
/// Descends the token into expansions, returning the tokens that matches the input
895944
/// token's [`SyntaxKind`] and text.
896945
pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
@@ -914,6 +963,32 @@ impl<'db> SemanticsImpl<'db> {
914963
r
915964
}
916965

966+
/// Descends the token into expansions, returning the tokens that matches the input
967+
/// token's [`SyntaxKind`] and text.
968+
pub fn descend_into_macros_exact_with_file(
969+
&self,
970+
token: SyntaxToken,
971+
) -> SmallVec<[InFile<SyntaxToken>; 1]> {
972+
let mut r = smallvec![];
973+
let text = token.text();
974+
let kind = token.kind();
975+
976+
self.descend_into_macros_cb(token.clone(), |InFile { value, file_id }, ctx| {
977+
let mapped_kind = value.kind();
978+
let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
979+
let matches = (kind == mapped_kind || any_ident_match())
980+
&& text == value.text()
981+
&& !ctx.is_opaque(self.db.upcast());
982+
if matches {
983+
r.push(InFile { value, file_id });
984+
}
985+
});
986+
if r.is_empty() {
987+
r.push(self.wrap_token_infile(token));
988+
}
989+
r
990+
}
991+
917992
/// Descends the token into expansions, returning the first token that matches the input
918993
/// token's [`SyntaxKind`] and text.
919994
pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
@@ -945,10 +1020,18 @@ impl<'db> SemanticsImpl<'db> {
9451020
&self,
9461021
InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
9471022
f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>,
1023+
) -> Option<T> {
1024+
self.descend_into_macros_impl_(InFile::new(file_id.into(), token), f)
1025+
}
1026+
1027+
fn descend_into_macros_impl_<T>(
1028+
&self,
1029+
InFile { value: token, file_id }: InFile<SyntaxToken>,
1030+
f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>,
9481031
) -> Option<T> {
9491032
let _p = tracing::info_span!("descend_into_macros_impl").entered();
9501033

951-
let span = self.db.real_span_map(file_id).span_for_range(token.text_range());
1034+
let span = self.db.span_map(file_id).span_for_range(token.text_range());
9521035

9531036
// Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
9541037
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
@@ -972,17 +1055,16 @@ impl<'db> SemanticsImpl<'db> {
9721055
// the tokens themselves aren't that interesting as the span that is being used to map
9731056
// things down never changes.
9741057
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![];
975-
let include = self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id);
1058+
let include = file_id.file_id().and_then(|file_id| {
1059+
self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id)
1060+
});
9761061
match include {
9771062
Some(include) => {
9781063
// include! inputs are always from real files, so they only need to be handled once upfront
9791064
process_expansion_for_token(&mut stack, include)?;
9801065
}
9811066
None => {
982-
stack.push((
983-
file_id.into(),
984-
smallvec![(token, SyntaxContextId::root(file_id.edition()))],
985-
));
1067+
stack.push((file_id, smallvec![(token, span.ctx)]));
9861068
}
9871069
}
9881070

@@ -1648,6 +1730,16 @@ impl<'db> SemanticsImpl<'db> {
16481730
self.with_ctx(|ctx| ctx.file_to_def(file).to_owned()).into_iter().map(Module::from)
16491731
}
16501732

1733+
fn hir_file_to_module_defs(&self, file: HirFileId) -> impl Iterator<Item = Module> {
1734+
self.with_ctx(|ctx| {
1735+
// FIXME: Do this properly
1736+
ctx.file_to_def(file.original_file_respecting_includes(ctx.db.upcast()).file_id())
1737+
.to_owned()
1738+
})
1739+
.into_iter()
1740+
.map(Module::from)
1741+
}
1742+
16511743
pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> {
16521744
self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope {
16531745
db: self.db,

crates/ide-assists/src/handlers/bool_to_enum.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ fn replace_usages(
208208
target_module: &hir::Module,
209209
delayed_mutations: &mut Vec<(ImportScope, ast::Path)>,
210210
) {
211-
for (file_id, references) in usages {
211+
for (file_id, references) in usages.map_out_of_macros(&ctx.sema) {
212212
edit.edit_file(file_id.file_id());
213213

214214
let refs_with_imports = augment_references_with_imports(ctx, references, target_module);

crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ fn edit_struct_references(
147147
};
148148
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
149149

150-
for (file_id, refs) in usages {
150+
for (file_id, refs) in usages.map_out_of_macros(&ctx.sema) {
151151
edit.edit_file(file_id.file_id());
152152
for r in refs {
153153
process_struct_name_reference(ctx, r, edit);
@@ -225,7 +225,7 @@ fn edit_field_references(
225225
};
226226
let def = Definition::Field(field);
227227
let usages = def.usages(&ctx.sema).all();
228-
for (file_id, refs) in usages {
228+
for (file_id, refs) in usages.map_out_of_macros(&ctx.sema) {
229229
edit.edit_file(file_id.file_id());
230230
for r in refs {
231231
if let Some(name_ref) = r.name.as_name_ref() {

0 commit comments

Comments
 (0)