Skip to content

Commit 2366c16

Browse files
committed
Fix eager token mapping panics
1 parent cabe26c commit 2366c16

File tree

7 files changed

+147
-95
lines changed

7 files changed

+147
-95
lines changed

crates/hir-expand/src/db.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ fn macro_arg(
338338
) -> Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>> {
339339
let loc = db.lookup_intern_macro_call(id);
340340

341-
if let Some(EagerCallInfo { arg, arg_id: Some(_), error: _ }) = loc.eager.as_deref() {
341+
if let Some(EagerCallInfo { arg, arg_id: _, error: _ }) = loc.eager.as_deref() {
342342
return Some(Arc::new((arg.0.clone(), arg.1.clone(), Default::default())));
343343
}
344344

@@ -404,7 +404,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy
404404

405405
fn macro_arg_text(db: &dyn ExpandDatabase, id: MacroCallId) -> Option<GreenNode> {
406406
let loc = db.lookup_intern_macro_call(id);
407-
let arg = loc.kind.arg(db)?;
407+
let arg = loc.kind.arg(db)?.value;
408408
if matches!(loc.kind, MacroCallKind::FnLike { .. }) {
409409
let first = arg.first_child_or_token().map_or(T![.], |it| it.kind());
410410
let last = arg.last_child_or_token().map_or(T![.], |it| it.kind());
@@ -490,8 +490,14 @@ fn macro_def(db: &dyn ExpandDatabase, id: MacroDefId) -> TokenExpander {
490490
fn macro_expand(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
491491
let _p = profile::span("macro_expand");
492492
let loc = db.lookup_intern_macro_call(id);
493+
494+
// This might look a bit odd, but we do not expand the inputs to eager macros here.
495+
// Eager macros inputs are expanded, well, eagerly when we collect the macro calls.
496+
// That kind of expansion uses the ast id map of an eager macros input though which goes through
497+
// the HirFileId machinery. As eager macro inputs are assigned a macro file id that query
498+
// will end up going through here again, whereas we want to just want to inspect the raw input.
499+
// As such we just return the input subtree here.
493500
if let Some(EagerCallInfo { arg, arg_id: None, error }) = loc.eager.as_deref() {
494-
// This is an input expansion for an eager macro. These are already pre-expanded
495501
return ExpandResult { value: Arc::new(arg.0.clone()), err: error.clone() };
496502
}
497503

crates/hir-expand/src/eager.rs

+27-16
Original file line numberDiff line numberDiff line change
@@ -67,26 +67,37 @@ pub fn expand_eager_macro_input(
6767
})),
6868
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr },
6969
});
70-
let arg_as_expr = match db.macro_arg_text(arg_id) {
71-
Some(it) => it,
72-
None => {
73-
return Ok(ExpandResult {
74-
value: None,
75-
err: Some(ExpandError::other("invalid token tree")),
76-
})
77-
}
70+
71+
let ExpandResult { value: expanded_eager_input, err } = {
72+
let arg_as_expr = match db.macro_arg_text(arg_id) {
73+
Some(it) => it,
74+
None => {
75+
return Ok(ExpandResult {
76+
value: None,
77+
err: Some(ExpandError::other("invalid token tree")),
78+
})
79+
}
80+
};
81+
82+
eager_macro_recur(
83+
db,
84+
&Hygiene::new(db, macro_call.file_id),
85+
InFile::new(arg_id.as_file(), SyntaxNode::new_root(arg_as_expr)),
86+
krate,
87+
resolver,
88+
)?
7889
};
79-
let ExpandResult { value: expanded_eager_input, err } = eager_macro_recur(
80-
db,
81-
&Hygiene::new(db, macro_call.file_id),
82-
InFile::new(arg_id.as_file(), SyntaxNode::new_root(arg_as_expr)),
83-
krate,
84-
resolver,
85-
)?;
90+
8691
let Some(expanded_eager_input) = expanded_eager_input else {
8792
return Ok(ExpandResult { value: None, err });
8893
};
89-
let (mut subtree, token_map) = mbe::syntax_node_to_token_tree(&expanded_eager_input);
94+
// FIXME: This token map is pointless, it points into the expanded eager syntax node, but that
95+
// node doesn't exist outside this function so we can't use this tokenmap.
96+
// Ideally we'd need to patch the tokenmap of the pre-expanded input and then put that here
97+
// or even better, forego expanding into a SyntaxNode altogether and instead construct a subtree
98+
// in place! But that is kind of difficult.
99+
let (mut subtree, _token_map) = mbe::syntax_node_to_token_tree(&expanded_eager_input);
100+
let token_map = Default::default();
90101
subtree.delimiter = crate::tt::Delimiter::unspecified();
91102

92103
let loc = MacroCallLoc {

crates/hir-expand/src/hygiene.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,12 @@ impl HygieneInfo {
149149
token_id = unshifted;
150150
(&attr_args.1, self.attr_input_or_mac_def_start?)
151151
}
152-
None => (
153-
&self.macro_arg.1,
154-
InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()),
155-
),
152+
None => (&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start())),
156153
},
157154
_ => match origin {
158-
mbe::Origin::Call => (
159-
&self.macro_arg.1,
160-
InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()),
161-
),
155+
mbe::Origin::Call => {
156+
(&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start()))
157+
}
162158
mbe::Origin::Def => match (&self.macro_def, &self.attr_input_or_mac_def_start) {
163159
(TokenExpander::DeclarativeMacro(expander), Some(tt)) => {
164160
(&expander.def_site_token_map, *tt)

crates/hir-expand/src/lib.rs

+94-60
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,9 @@ pub enum MacroDefKind {
154154
struct EagerCallInfo {
155155
/// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro!
156156
arg: Arc<(tt::Subtree, TokenMap)>,
157-
/// call id of the eager macro's input file. If this is none, macro call containing this call info
158-
/// is an eager macro's input, otherwise it is its output.
157+
/// Call id of the eager macro's input file (this is the macro file for its fully expanded input).
158+
/// If this is none, `arg` contains the pre-expanded input, otherwise arg contains the
159+
/// post-expanded input.
159160
arg_id: Option<MacroCallId>,
160161
error: Option<ExpandError>,
161162
}
@@ -270,53 +271,7 @@ impl HirFileId {
270271
/// Return expansion information if it is a macro-expansion file
271272
pub fn expansion_info(self, db: &dyn db::ExpandDatabase) -> Option<ExpansionInfo> {
272273
let macro_file = self.macro_file()?;
273-
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
274-
275-
let arg_tt = loc.kind.arg(db)?;
276-
277-
let macro_def = db.macro_def(loc.def);
278-
let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
279-
let macro_arg = db.macro_arg(macro_file.macro_call_id).unwrap_or_else(|| {
280-
Arc::new((
281-
tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
282-
Default::default(),
283-
Default::default(),
284-
))
285-
});
286-
287-
let def = loc.def.ast_id().left().and_then(|id| {
288-
let def_tt = match id.to_node(db) {
289-
ast::Macro::MacroRules(mac) => mac.token_tree()?,
290-
ast::Macro::MacroDef(_) if matches!(macro_def, TokenExpander::BuiltInAttr(_)) => {
291-
return None
292-
}
293-
ast::Macro::MacroDef(mac) => mac.body()?,
294-
};
295-
Some(InFile::new(id.file_id, def_tt))
296-
});
297-
let attr_input_or_mac_def = def.or_else(|| match loc.kind {
298-
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
299-
// FIXME: handle `cfg_attr`
300-
let tt = ast_id
301-
.to_node(db)
302-
.doc_comments_and_attrs()
303-
.nth(invoc_attr_index.ast_index())
304-
.and_then(Either::left)?
305-
.token_tree()?;
306-
Some(InFile::new(ast_id.file_id, tt))
307-
}
308-
_ => None,
309-
});
310-
311-
Some(ExpansionInfo {
312-
expanded: InFile::new(self, parse.syntax_node()),
313-
arg: InFile::new(loc.kind.file_id(), arg_tt),
314-
attr_input_or_mac_def,
315-
macro_arg_shift: mbe::Shift::new(&macro_arg.0),
316-
macro_arg,
317-
macro_def,
318-
exp_map,
319-
})
274+
ExpansionInfo::new(db, macro_file)
320275
}
321276

322277
pub fn as_builtin_derive_attr_node(
@@ -603,13 +558,18 @@ impl MacroCallKind {
603558
FileRange { range, file_id }
604559
}
605560

606-
fn arg(&self, db: &dyn db::ExpandDatabase) -> Option<SyntaxNode> {
561+
fn arg(&self, db: &dyn db::ExpandDatabase) -> Option<InFile<SyntaxNode>> {
607562
match self {
608-
MacroCallKind::FnLike { ast_id, .. } => {
609-
Some(ast_id.to_node(db).token_tree()?.syntax().clone())
563+
MacroCallKind::FnLike { ast_id, .. } => ast_id
564+
.to_in_file_node(db)
565+
.map(|it| Some(it.token_tree()?.syntax().clone()))
566+
.transpose(),
567+
MacroCallKind::Derive { ast_id, .. } => {
568+
Some(ast_id.to_in_file_node(db).syntax().cloned())
569+
}
570+
MacroCallKind::Attr { ast_id, .. } => {
571+
Some(ast_id.to_in_file_node(db).syntax().cloned())
610572
}
611-
MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
612-
MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
613573
}
614574
}
615575
}
@@ -627,7 +587,7 @@ impl MacroCallId {
627587
/// ExpansionInfo mainly describes how to map text range between src and expanded macro
628588
#[derive(Debug, Clone, PartialEq, Eq)]
629589
pub struct ExpansionInfo {
630-
expanded: InFile<SyntaxNode>,
590+
expanded: InMacroFile<SyntaxNode>,
631591
/// The argument TokenTree or item for attributes
632592
arg: InFile<SyntaxNode>,
633593
/// The `macro_rules!` or attribute input.
@@ -643,7 +603,7 @@ pub struct ExpansionInfo {
643603

644604
impl ExpansionInfo {
645605
pub fn expanded(&self) -> InFile<SyntaxNode> {
646-
self.expanded.clone()
606+
self.expanded.clone().into()
647607
}
648608

649609
pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
@@ -674,7 +634,7 @@ impl ExpansionInfo {
674634
let token_id_in_attr_input = if let Some(item) = item {
675635
// check if we are mapping down in an attribute input
676636
// this is a special case as attributes can have two inputs
677-
let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
637+
let call_id = self.expanded.file_id.macro_call_id;
678638
let loc = db.lookup_intern_macro_call(call_id);
679639

680640
let token_range = token.value.text_range();
@@ -720,7 +680,7 @@ impl ExpansionInfo {
720680
let relative_range =
721681
token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
722682
let token_id = self.macro_arg.1.token_by_range(relative_range)?;
723-
// conditionally shift the id by a declaratives macro definition
683+
// conditionally shift the id by a declarative macro definition
724684
self.macro_def.map_id_down(token_id)
725685
}
726686
};
@@ -730,7 +690,7 @@ impl ExpansionInfo {
730690
.ranges_by_token(token_id, token.value.kind())
731691
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
732692

733-
Some(tokens.map(move |token| self.expanded.with_value(token)))
693+
Some(tokens.map(move |token| InFile::new(self.expanded.file_id.into(), token)))
734694
}
735695

736696
/// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion.
@@ -739,12 +699,13 @@ impl ExpansionInfo {
739699
db: &dyn db::ExpandDatabase,
740700
token: InFile<&SyntaxToken>,
741701
) -> Option<(InFile<SyntaxToken>, Origin)> {
702+
assert_eq!(token.file_id, self.expanded.file_id.into());
742703
// Fetch the id through its text range,
743704
let token_id = self.exp_map.token_by_range(token.value.text_range())?;
744705
// conditionally unshifting the id to accommodate for macro-rules def site
745706
let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
746707

747-
let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
708+
let call_id = self.expanded.file_id.macro_call_id;
748709
let loc = db.lookup_intern_macro_call(call_id);
749710

750711
// Special case: map tokens from `include!` expansions to the included file
@@ -794,6 +755,63 @@ impl ExpansionInfo {
794755
tt.value.covering_element(range + tt.value.text_range().start()).into_token()?;
795756
Some((tt.with_value(token), origin))
796757
}
758+
759+
fn new(db: &dyn db::ExpandDatabase, macro_file: MacroFile) -> Option<ExpansionInfo> {
760+
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
761+
762+
let arg_tt = loc.kind.arg(db)?;
763+
764+
let macro_def = db.macro_def(loc.def);
765+
let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
766+
let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() };
767+
768+
let macro_arg = db
769+
.macro_arg(match loc.eager.as_deref() {
770+
Some(&EagerCallInfo { arg_id: Some(_), .. }) => return None,
771+
_ => macro_file.macro_call_id,
772+
})
773+
.unwrap_or_else(|| {
774+
Arc::new((
775+
tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
776+
Default::default(),
777+
Default::default(),
778+
))
779+
});
780+
781+
let def = loc.def.ast_id().left().and_then(|id| {
782+
let def_tt = match id.to_node(db) {
783+
ast::Macro::MacroRules(mac) => mac.token_tree()?,
784+
ast::Macro::MacroDef(_) if matches!(macro_def, TokenExpander::BuiltInAttr(_)) => {
785+
return None
786+
}
787+
ast::Macro::MacroDef(mac) => mac.body()?,
788+
};
789+
Some(InFile::new(id.file_id, def_tt))
790+
});
791+
let attr_input_or_mac_def = def.or_else(|| match loc.kind {
792+
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
793+
// FIXME: handle `cfg_attr`
794+
let tt = ast_id
795+
.to_node(db)
796+
.doc_comments_and_attrs()
797+
.nth(invoc_attr_index.ast_index())
798+
.and_then(Either::left)?
799+
.token_tree()?;
800+
Some(InFile::new(ast_id.file_id, tt))
801+
}
802+
_ => None,
803+
});
804+
805+
Some(ExpansionInfo {
806+
expanded,
807+
arg: arg_tt,
808+
attr_input_or_mac_def,
809+
macro_arg_shift: mbe::Shift::new(&macro_arg.0),
810+
macro_arg,
811+
macro_def,
812+
exp_map,
813+
})
814+
}
797815
}
798816

799817
/// `AstId` points to an AST node in any file.
@@ -805,6 +823,9 @@ impl<N: AstIdNode> AstId<N> {
805823
pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> N {
806824
self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
807825
}
826+
pub fn to_in_file_node(&self, db: &dyn db::ExpandDatabase) -> InFile<N> {
827+
InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
828+
}
808829
pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> AstPtr<N> {
809830
db.ast_id_map(self.file_id).get(self.value)
810831
}
@@ -820,6 +841,7 @@ impl ErasedAstId {
820841
db.ast_id_map(self.file_id).get_raw(self.value)
821842
}
822843
}
844+
823845
/// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
824846
///
825847
/// Typical usages are:
@@ -1038,6 +1060,18 @@ impl InFile<SyntaxToken> {
10381060
}
10391061
}
10401062

1063+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
1064+
pub struct InMacroFile<T> {
1065+
pub file_id: MacroFile,
1066+
pub value: T,
1067+
}
1068+
1069+
impl<T> From<InMacroFile<T>> for InFile<T> {
1070+
fn from(macro_file: InMacroFile<T>) -> Self {
1071+
InFile { file_id: macro_file.file_id.into(), value: macro_file.value }
1072+
}
1073+
}
1074+
10411075
fn ascend_node_border_tokens(
10421076
db: &dyn db::ExpandDatabase,
10431077
InFile { file_id, value: node }: InFile<&SyntaxNode>,

crates/ide/src/syntax_highlighting.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,14 @@ fn traverse(
265265

266266
// set macro and attribute highlighting states
267267
match event.clone() {
268-
Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
268+
Enter(NodeOrToken::Node(node))
269+
if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
270+
{
269271
tt_level += 1;
270272
}
271-
Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
273+
Leave(NodeOrToken::Node(node))
274+
if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
275+
{
272276
tt_level -= 1;
273277
}
274278
Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
@@ -387,7 +391,7 @@ fn traverse(
387391
};
388392
let descended_element = if in_macro {
389393
// Attempt to descend tokens into macro-calls.
390-
match element {
394+
let res = match element {
391395
NodeOrToken::Token(token) if token.kind() != COMMENT => {
392396
let token = match attr_or_derive_item {
393397
Some(AttrOrDerive::Attr(_)) => {
@@ -412,7 +416,8 @@ fn traverse(
412416
}
413417
}
414418
e => e,
415-
}
419+
};
420+
res
416421
} else {
417422
element
418423
};

0 commit comments

Comments
 (0)