Skip to content
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

Add expansion infrastructure for derive macros #2479

Merged
merged 4 commits into from
Dec 5, 2019

Conversation

flodiebold
Copy link
Member

I thought I'd experiment a bit with attribute macro/derive expansion, and here's what I've got so far. It has dummy implementations of the Copy / Clone derives, to show that the approach works; it doesn't add any attribute macro support, but I think that fits into the architecture.

Basically, during raw item collection, we look at the attributes and generate macro calls for them if necessary. Currently I only do this for derives, and just add the derive macro calls as separate calls next to the item. I think for derives, it's important that they don't obscure the actual item, since they can't actually change it (e.g. sending the item token tree through macro expansion unnecessarily might make completion within it more complicated).

Attribute macros would have to be recognized at that stage and replace the item (i.e., the raw item collector will just emit an attribute macro call, and not the item). I think when we implement this, we should try to recognize known inert attributes, so that we don't do macro expansion unnecessarily; anything that isn't known needs to be treated as a possible attribute macro call (since the raw item collector can't resolve the macro yet).

There's basically no name resolution for attribute macros implemented, I just hardcoded the built-in derives. In the future, the built-ins should work within the normal name resolution infrastructure; the problem there is that the builtin stubs in std use macros 2.0, which we don't support yet (and adding support is outside the scope of this).

One aspect that I don't really have a solution for, but I don't know how important it is, is removing the attribute itself from its input. I'm pretty sure rustc leaves out the attribute macro from the input, but to do that, we'd have to create a completely new syntax node. I guess we could do it when / after converting to a token tree.

Copy link
Member

@matklad matklad left a comment

Choose a reason for hiding this comment

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

Overall, this looks good to me, but I think attribute -> macro call converssion should happen after raw items step: I believe in general one needs name resolution to figure out if an attribute is a macro or not, and we can't have that in RawItems.

it might also be a good idea to at least extract the correct name in the dummy impl, just to make sure that we correctly thread the right syntax tree.

The question of stripping attributes is interesting... In general, I believe we would have to really create new syntax nodes and basically treat attributed items as

my_macro! {
    fn item_goes_here() {}
}

completion would then have to just deal with it, by merging syntax trees at the call-site and def-site.

crates/ra_hir_def/src/attr.rs Outdated Show resolved Hide resolved
pub(super) path: Path,
/// Whether this is a derive invocation. If so, `path` is just the trait path.
pub(super) derive: bool,
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need to have this at the raw_items layer? Raw items already have attributes, so it seems like we could and should deal with it at the DefCollector layer? I think that becomes required when we add proper name resolution to the mix.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we need to treat any unknown attribute as a macro invocation anyway. I'll try whether it's simpler to do it in the def collector, though.

Copy link
Member Author

Choose a reason for hiding this comment

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

we need to treat any unknown attribute as a macro invocation anyway

Hmm, we might be able to figure out that some attributes are inert using name resolution though, so that's a good point 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that works, actually...

@@ -53,7 +53,7 @@ impl AstIdMap {
pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap {
assert!(node.parent().is_none());
let mut res = AstIdMap { arena: Arena::default() };
// By walking the tree in bread-first order we make sure that parents
Copy link
Member

Choose a reason for hiding this comment

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

lol :D

crates/ra_hir_expand/src/lib.rs Show resolved Hide resolved
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroCallKind {
FnLike(AstId<ast::MacroCall>),
Attr(AstId<ast::ModuleItem>),
Copy link
Member

Choose a reason for hiding this comment

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

This probably should include some kind of index, to tell which attribute on the item this is.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, or maybe not? Attributes are expanded in order, so this can always mean first attribute

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it'd always be the first (non-inert one). If you have multiple macro attributes on an item, the first one gets applied, and then the second macro call happens inside the expansion 'file', right?

Copy link
Member

Choose a reason for hiding this comment

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

This is "mostly right" I think modulo, ahem, some backwards compatibility cases: rust-lang/rust#66529 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, derive helper attributes are very weird anyway. The derive helper attribute is inert though, so it's still the first non-inert one ;)

@flodiebold
Copy link
Member Author

it might also be a good idea to at least extract the correct name in the dummy impl, just to make sure that we correctly thread the right syntax tree.

Oh yeah, I was planning on implementing them a bit more properly if you think the approach makes sense 🙂

self.def_collector.unexpanded_attribute_macros.push((self.module_id, ast_id, path));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this looks better!

@flodiebold flodiebold marked this pull request as ready for review December 5, 2019 18:30
@flodiebold
Copy link
Member Author

@matklad ok, implemented the derives more 'properly' now. I had to adapt the syntax node -> token tree conversion a bit because the parser can't deal with the invisible delimiters, not completely sure about that.

Since as long as we're not implementing the bodies, they all work the same way.
@flodiebold
Copy link
Member Author

Actually I just added all the other built-in derives, since the stubs look the same for all of them...

@matklad
Copy link
Member

matklad commented Dec 5, 2019

image

😍

bors merge

💯

bors bot added a commit that referenced this pull request Dec 5, 2019
2479: Add expansion infrastructure for derive macros r=matklad a=flodiebold

I thought I'd experiment a bit with attribute macro/derive expansion, and here's what I've got so far. It has dummy implementations of the Copy / Clone derives, to show that the approach works; it doesn't add any attribute macro support, but I think that fits into the architecture.

Basically, during raw item collection, we look at the attributes and generate macro calls for them if necessary. Currently I only do this for derives, and just add the derive macro calls as separate calls next to the item. I think for derives, it's important that they don't obscure the actual item, since they can't actually change it (e.g. sending the item token tree through macro expansion unnecessarily might make completion within it more complicated).

Attribute macros would have to be recognized at that stage and replace the item (i.e., the raw item collector will just emit an attribute macro call, and not the item). I think when we implement this, we should try to recognize known inert attributes, so that we don't do macro expansion unnecessarily; anything that isn't known needs to be treated as a possible attribute macro call (since the raw item collector can't resolve the macro yet).

There's basically no name resolution for attribute macros implemented, I just hardcoded the built-in derives. In the future, the built-ins should work within the normal name resolution infrastructure; the problem there is that the builtin stubs in `std` use macros 2.0, which we don't support yet (and adding support is outside the scope of this).

One aspect that I don't really have a solution for, but I don't know how important it is, is removing the attribute itself from its input. I'm pretty sure rustc leaves out the attribute macro from the input, but to do that, we'd have to create a completely new syntax node. I guess we could do it when / after converting to a token tree.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
@bors
Copy link
Contributor

bors bot commented Dec 5, 2019

Build succeeded

  • TypeScript
  • Rust

@bors bors bot merged commit 1069704 into rust-lang:master Dec 5, 2019
@flodiebold flodiebold deleted the derive branch December 5, 2019 20:09
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.

2 participants