Skip to content

Conversation

@Veykril
Copy link
Member

@Veykril Veykril commented Aug 21, 2021

image

The token mapping for items with attributes got overwritten partially by the attributes non-item input, since attributes have two different inputs, the item and the direct input both.
This PR gives attributes a second TokenMap for its direct input. We now shift all normal input IDs by the item input maximum(we maybe wanna swap this see below) similar to what we do for macro-rules/def. For mapping down we then have to figure out whether we are inside the direct attribute input or its item input to pick the appropriate mapping which can be done with some token range comparisons.

Fixes #9867

@Veykril Veykril marked this pull request as ready for review August 21, 2021 16:09
Comment on lines +374 to +413
let token_id = if let Some(item) = item {
let call_id = match self.expanded.file_id.0 {
HirFileIdRepr::FileId(_) => return None,
HirFileIdRepr::MacroFile(macro_file) => macro_file.macro_call_id,
};
let loc = db.lookup_intern_macro(call_id);

let token_range = token.value.text_range();
match &loc.kind {
MacroCallKind::Attr { attr_args, invoc_attr_index, .. } => {
let attr = item.attrs().nth(*invoc_attr_index as usize)?;
match attr.token_tree() {
Some(token_tree)
if token_tree.syntax().text_range().contains_range(token_range) =>
{
let attr_input_start =
token_tree.left_delimiter_token()?.text_range().start();
let range = token.value.text_range().checked_sub(attr_input_start)?;
let token_id =
self.macro_arg_shift.shift(attr_args.1.token_by_range(range)?);
Some(token_id)
}
_ => None,
}
}
_ => None,
}
} else {
None
};

let token_id = match token_id {
Some(token_id) => token_id,
None => {
let range =
token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
let token_id = self.macro_arg.1.token_by_range(range)?;
self.macro_def.map_id_down(token_id)
}
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual mapping is unfortunately now split a bit(that is between attributes and macrodef/macrorules mapping which happens in macro_def.map_id_down) and I don't think we can easily fix this up as attributes need a lot more info here. Maybe someone else sees a way to refactor this better.
Currently this means speculative expansion needs to reimplement this mapping as well.

@Veykril Veykril changed the title Map attribute input tokens correctly feat: Map attribute input tokens and correctly map attribute item tokens Aug 21, 2021
MacroCallKind::Attr { attr_args, .. } => Some(attr_args),
MacroCallKind::Attr { attr_args, .. } => {
let mut attr_args = attr_args.0.clone();
mbe::Shift::new(&macro_arg.0).shift_all(&mut attr_args);
Copy link
Member Author

@Veykril Veykril Aug 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a new shift instance on every expansion, is this fine?

If not we can either have the macro_arg query return a Shift instance which would be only relevant to attribute macros.
Or swap things around, shift the attribute item input IDs by the normal attribute input IDs and store the Shift in the MacroCallKind::Attr. Though in this case we would have to clonse the entire item TokenTree.

I'm unsure which approach is best of the three(the current one and the two proposed ones) but I believe turning the shifting around and storing in MacroCallKind::Attr might be best?

@Veykril Veykril changed the title feat: Map attribute input tokens and correctly map attribute item tokens feat: Map attribute input tokens, and correctly map attribute item tokens Aug 21, 2021
@Veykril Veykril changed the title feat: Map attribute input tokens, and correctly map attribute item tokens feat: Implement attribute input token mapping, fix attribute item token mapping Aug 21, 2021
@matklad
Copy link
Contributor

matklad commented Aug 24, 2021

Haven't read this through yet, but my gut feeling is that we should be able to use existing infra without modifications. I don't immediately see the place where we produce two inputs for proc macro, but can we modify that place to use disjoint ids? In some sense, can we say that the input to proc macro is always a single subtree with none parens and two children (the attribute and the item)?

Again, don't yet understand the code, might be talking nonsense here!

@matklad
Copy link
Contributor

matklad commented Aug 24, 2021

Ah, I see now! Here

https://github.com/matklad/rust-analyzer/blob/d1cd81f3874cb71a514427060e037b4dcae9b814/crates/hir_expand/src/db.rs#L378-L392

we get two arguments from separate code paths. The item is macro_arg, and attr_args we get from macro id. I wonder why is that? Naively, I'd expect both to be a part of macro_arg

@matklad
Copy link
Contributor

matklad commented Aug 24, 2021

I wonder why is that?

Ah, so the thing is, becaues we don't know if attr's arg is actually the macro input. Things like #[allow(missing_docs)] are not macros, and we want to preserve then in hir. That's why we store argument as a tt in AttrInput, and that's what we re-use in expansion. And, indeed, tokens from AttrInput are missing any kind of token map.

Literal(SmolStr),
/// `#[attr(subtree)]`
TokenTree(Subtree),
TokenTree(tt::Subtree, mbe::TokenMap),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it seems like we need to add TokenMap here, this is a fundamentally missing info.

@matklad
Copy link
Contributor

matklad commented Aug 24, 2021

Can we add a test here?

@Veykril
Copy link
Member Author

Veykril commented Aug 25, 2021

We don't really have a testing infra for mapping yet do we? Guess I'll look into that first then.

@Veykril
Copy link
Member Author

Veykril commented Aug 27, 2021

After looking around a bit I'm actually not really sure how we would test the token mappings for proc-macros 🤔

@matklad
Copy link
Contributor

matklad commented Aug 27, 2021

It’s ok to merge with s without a test then!

Though having some completion test with proc macros might be nice! We don’t need to test token maps directly, we want to test hat ide features work

@Veykril
Copy link
Member Author

Veykril commented Aug 27, 2021

👍

Having completion tests with proc-macros involved would be nice, though I do wonder how we can realize that.

bors r+

@bors
Copy link
Contributor

bors bot commented Aug 27, 2021

@bors bors bot merged commit 97409e5 into rust-lang:master Aug 27, 2021
@Veykril Veykril deleted the attr-mapping branch August 27, 2021 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect token mapping in attribute macros

2 participants