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

[WIP] Expand #[derive(..)]s in the InvocationCollector #39391

Closed
wants to merge 18 commits into from

Conversation

keeperofdakeys
Copy link
Contributor

Expand derive attributes in the InvocationCollector

r? @jseyfried

@keeperofdakeys
Copy link
Contributor Author

@jseyfried This code could is a bit rough, and it currently has a bug that means it only works with legacy custom derives, and builtin derives. I'm hoping that a second set of eyes will help me spot the error I'm making.

@keeperofdakeys
Copy link
Contributor Author

cc #38356

@keeperofdakeys
Copy link
Contributor Author

Cause of bug:

22:21 < jseyfried> The problem is that no proc macro derives have been imported yet when we are doing invocation collection

pub fn is_derive_attr(attr: &ast::Attribute) -> bool {
let res = attr.name() == Symbol::intern("derive");
res
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This can just be attr.name() == "derive" (I would inline and remove the function)

}

pub fn get_derive_attr(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>,
is_derive_type: fn(&mut ExtCtxt, &NestedMetaItem) -> bool)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be a little cleaner if this were an enum (three variants) and is_derive_type(...) were a match.

Annotatable::TraitItem(P(fully_configure!(self, item, noop_fold_trait_item)));
return self.collect_attr(attr, item, ExpansionKind::TraitItems).make_trait_items()
} else {
self.cx.span_err(attr.span, "`derive` can't be only be applied to items");
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we move this logic into collect_attr?
e.g.

if attr.name() == "derive" && (kind == ExpansionKind::TraitItems || ExpansionKind::ImplItems) { ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What would you return fromcollect_attr for the error case? The callers assume that collect_attr will always return a valid Expansion.

Copy link
Contributor

Choose a reason for hiding this comment

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

It could just return the original item (via kind.expect_from_annotatables(iter::once(item))).

@@ -54,7 +54,7 @@ impl MultiItemModifier for CustomDerive {
Annotatable::Item(item) => item,
Annotatable::ImplItem(_) |
Annotatable::TraitItem(_) => {
ecx.span_err(span, "custom derive attributes may only be \
ecx.span_err(span, "proc_macro_derive attributes may only be \
Copy link
Contributor

Choose a reason for hiding this comment

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

"proc_macro_derive attribute" makes me think of #[proc_macro_derive] itself rather than #[derive(Trait)].
Maybe "proc-macro derives may only be ..."?

}

pub fn verify_derive_attrs(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>) {
for i in 0..attrs.len() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for attr in attrs { ... } is more idiomatic

get_proc_macro_derive(self.cx, &mut attrs)
}).or_else(|| {
add_structural_marker(self.cx, &mut attrs);
add_copy_clone_marker(self.cx, &mut attrs);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could merge these two function into a single function add_derived_markers that computes a Vec<Name> (or Vec<MetaItem>) of all the derived trait names and then just uses e.g. vec.contains(&Symbol::intern("Copy")) || vec.contains(&Symbol::intern("Clone"))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One annoying thing is that currently we need some span to add for the new attribute. If we make a Vec<Name>, we'll either need to grab the span of the first attribute, or if possible make a dummy span.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think either would be fine (provided the span is allow_internal_unstable).

@keeperofdakeys
Copy link
Contributor Author

keeperofdakeys commented Jan 30, 2017

We're kind of breaking the semantics of the InvocationCollector with this change, which causes serious breakage. We're trying to resolve derives during collection, when the InvocationCollector usually resolves separately itself. With Foo as a proc_macro_derive, this works: #[derive(Copy, Foo), this doesn't: #[derive(Foo)] (it's never expanded at all) - as you suggested, this is probably because the first gives #[macro_use] time to be expanded.

The fix for this seems to be moving the resolution of types of derives into the invocation expansion loop. However I'm not sure how to do the ordering of derives if this is done. Maybe it's enough to resolve custom_derives in collect_attr (which become InvocKind::Attr anyway), and leave proc_macro_derive vs builtin to the resolution in the invocation loop. Though I'm not sure how detection of undefined derives works here.

This means that proc_macro_derives that haven't been imported
yet don't get ignored. This is safe because legacy_derives
and builtin derives are instantly resolvable.
return None;
}

let traits = attr.meta_item_list().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it might panic on #[derive].

return traits.get(0);
}

pub fn verify_derive_attrs(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think attrs: &[ast::Attribute] is sufficient here

}

impl DeriveType {
pub fn is_derive_type(&self, cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we instead have a method DeriveType::classify(name, cx) here? The function would check legacy first (returning DeriveType::Legacy if we find derive_* in builtin_macros), then check builtins (returning DeriveType::Builtin), then finally return DeriveType::ProcMacro.

e.g. is_legacy_derive(cx, titem) would become DeriveType::classify(titem.name(), cx) == DeriveType::Legacy.

Even better, we could move this logic into cx.resolver.find_attr_invoc() or a new method of ext::base::Resolver.


if !attrs.iter().any(|a| a.name() == "structural_match") &&
titems.iter().any(|t| *t == "PartialEq") && titems.iter().any(|t| *t == "Eq") {
let structural_match = Symbol::intern("structural_match");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: indent the if body one more space


if !attrs.iter().any(|a| a.name() == "rustc_copy_clone_marker") &&
titems.iter().any(|t| *t == "Copy") && titems.iter().any(|t| *t == "Clone") {
let structural_match = Symbol::intern("structural_match");
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here)

let tname = titem.name().unwrap();
let name = Symbol::intern(&format!("derive_{}", tname));
let path = ast::Path::from_ident(titem.span, ast::Ident::with_empty_ctxt(name));
cx.resolver.resolve_macro(cx.current_expansion.mark, &path, false).is_ok()
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we would just resolve in resolver.builtin_macros, either by moving this logic into resolve or adding a method to check builtin macros directly to ext::base::Resolver.

pub fn is_builtin_derive(cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
let tname = titem.name().unwrap();
let derive_mode = ast::Path::from_ident(titem.span, ast::Ident::with_empty_ctxt(tname));
cx.resolver.resolve_macro(cx.current_expansion.mark, &derive_mode, false).map(|ext| {
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here, re resolver.builtin_macros)

@@ -856,7 +957,8 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {

let (item, attr) = self.classify_item(item);
if let Some(attr) = attr {
let item = Annotatable::ImplItem(P(fully_configure!(self, item, noop_fold_impl_item)));
let item =
Annotatable::ImplItem(P(fully_configure!(self, item, noop_fold_impl_item)));
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: stray change

@keeperofdakeys
Copy link
Contributor Author

I implemented the change that you suggested, and all the serious test failures are now gone. It's down to trivial test failures, namely:

Using derive as a macro like this derive!() now gives the error macro undefined: 'derive!', when before it gave 'derive' can only be used in attributes. This is a natural consequence of removing derive as a resolvable macro. Should there be a dummy macro now, since you can't use derive as an attribute macro anyway?

An invalid derive attribute (like #[derive(Zero)]) now gives the error macro undefined: 'Zero!', where as before it gave '#[derive]' for custom traits is not stable enough for use. It is deprecated and will be removed in v1.15 (see issue #29644). Obviously an improvement, but could this be improved even more?

@jseyfried
Copy link
Contributor

jseyfried commented Jan 31, 2017

@keeperofdakeys

Should there be a dummy macro now, since you can't use derive as an attribute macro anyway?

I don't think it matters that much -- macro undefined: 'derive!' is fine.

but could [invalid derive attribute diagnostics] be improved even more?

Yeah, by changing from macro undefined: 'Zero!' to macro undefined: 'Zero' (removing !). Even better -- something like undefined custom derive `Zero` .

@keeperofdakeys
Copy link
Contributor Author

keeperofdakeys commented Jan 31, 2017

I've pushed some fixes for the latest test failures, there will probably be some more though.

@Arnavion
Copy link

What is the new error message for the situation described in #39326 ?

@keeperofdakeys
Copy link
Contributor Author

keeperofdakeys commented Jan 31, 2017 via email

Copy link
Contributor

@jseyfried jseyfried left a comment

Choose a reason for hiding this comment

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

r=me with tests passing (all comments optional)

tname
} else {
continue;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: using a match is more idiomatic here

let span = attrs[0].span;

if !attrs.iter().any(|a| a.name() == "structural_match") &&
titems.iter().any(|t| *t == "PartialEq") && titems.iter().any(|t| *t == "Eq") {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this line should be un-indented one space.

}

if !attrs.iter().any(|a| a.name() == "rustc_copy_clone_marker") &&
titems.iter().any(|t| *t == "Copy") && titems.iter().any(|t| *t == "Clone") {
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here)

-> Option<ast::Attribute> {
verify_derive_attrs(cx, attrs);
get_derive_attr(cx, attrs, DeriveType::Legacy).and_then(|a| {
let titem = derive_attr_trait(cx, &a);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: too much indent

return Some(cx.attribute(mitem.span, mitem));
}
}
None
Copy link
Contributor

Choose a reason for hiding this comment

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

Using an else to avoid the above return Some is more idiomatic

"custom_derive",
titem.span,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_CUSTOM_DERIVE
Copy link
Contributor

Choose a reason for hiding this comment

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

the above four lines should be un-indented a space

let name = Symbol::intern(&format!("derive_{}", tname));
if !cx.resolver.is_whitelisted_legacy_custom_derive(name) {
cx.span_warn(titem.span,
feature_gate::EXPLAIN_DEPR_CUSTOM_DERIVE);
Copy link
Contributor

Choose a reason for hiding this comment

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

(wrong indent)

@keeperofdakeys
Copy link
Contributor Author

Travis seems unhappy with this PR, but happy with others. So try creating a new PR for all this.

@keeperofdakeys
Copy link
Contributor Author

Moved to #39442

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.

3 participants