-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
resolve: Relax shadowing restrictions on macro-expanded macros #53778
Conversation
r? @eddyb (rust_highfive has picked a reviewer for you, use r? to override) |
r? @alexcrichton or @oli-obk |
cc @nikomatsakis @nrc, you may be interested in this as well. The resulting implemented rule was conservative - for macros 2.0 any macro-expanded macro could not shadow any other macro (legacy macros used more elaborate rules for backward compatibility). This PR refines that rule. The new rule is still simple enough (see the comments for |
The code changes seems reasonable here, but I'm no expert on the intricacies of macro resolution nor what the long-term future plans are. In that sense I'll defer to @nrc and others for the review of the purpose of the PR |
The code looks fine and the relaxed rules sound fine too, but I've really paged out name resolution, so I'm not 100% sure. Hopefully Niko can remember more than me. |
3530727
to
19aad80
Compare
Updated with tests demonstrating possible expansion/shadowing configurations. |
19aad80
to
5bd1974
Compare
resolve: Future proof resolutions for potentially built-in attributes Based on #53778 This is not full "pass all attributes through name resolution", but a more conservative solution. If built-in attribute is ambiguous with any other macro in scope, then an error is reported. TODO: Explain what complications arise with the full solution.
resolve: Future proof resolutions for potentially built-in attributes Based on #53778 This is not full "pass all attributes through name resolution", but a more conservative solution. If built-in attribute is ambiguous with any other macro in scope, then an error is reported. TODO: Explain what complications arise with the full solution. cc #50911 (comment) Closes #53531
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've not really read the code too deply, mostly the test cases. I'm not sure I really understand the rule yet! @petrochenkov maybe you can clarify?
src/librustc_resolve/macros.rs
Outdated
// define_mac!(); // if this generates another `macro_rules! mac`, then it can't shadow | ||
// // the outer `mac` and we have and ambiguity error | ||
// mac!(); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice comment. One thing that is not clear to me — is this a change in semantics that is put in place by this PR? Or just documenting how things work today?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This particular snippet is documentation of existing behavior (it's copied from the function directly above).
Changes in semantics (mostly for modern macro
macros) mostly appear when we put this snippet itself (or some parts of it) into a macro.
// `+?` - configuration possible only with legacy scoping | ||
|
||
// N | Outer ~ Invoc | Invoc ~ Inner | Outer ~ Inner | Possible | | ||
// 1 | < | < | < | + | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand this chart. I would assume that this means that "Inner takes precedence" -- e.g., because Outer < Invoc
. But check_1
below seems to report an error?
And what is N
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outer < Invoc
means expansion producing Outer
is less than expansion producing Invoc
.
Expansions form a tree (code produced by a macro expansion may contains multiple nested expansions and so on), and <
means strictly closer to the root of that tree.
N
is just the number of combination, from 1
to 4 * 4 * 4 = 64
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok, so this is like "can we construct a secnario where the outer is less than the invoc"... I see.
|
||
macro_rules! gen_inner_invoc { () => { | ||
macro_rules! m { () => {} } | ||
m!(); // OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably this expands to the m
that is defined on the line before? is there some way we can test that...?
For example:
struct Right;
struct Wrong;
fn check5() -> Right {
macro_rules! m { () => Wrong }
macro_rules! gen_inner_invoc { () => {
macro_rules! m { () => Right }
m!() // OK
}}
gen_inner_invoc!() // Look ma, no type error...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yes, it's probably not too hard to insert such Wrong
s for all "outer" shadowed macros, and Right
s for "inner" macros, for all // OK
cases.
|
||
macro_rules! gen_inner_gen_invoc { () => { | ||
macro_rules! m { () => {} } | ||
gen_invoc!(); // OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same resolution as before, I presume
// Suppose that we resolved macro invocation with `invoc_id` to binding `binding` at some | ||
// expansion round `max(invoc_id, binding)` when they both emerged from macros. | ||
// Then this function returns `true` if `self` may emerge from a macro *after* that | ||
// in some later round and screw up our previously found resolution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, can we improve this comment a bit?
For example, I am not clear on how you get an error -- the comment makes it sounds like returning true means that the result "may screw up our previously found resolution" -- so is that an error? Or does "may" here mean "is allowed to".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From inspecting the rest of the code, it seems like true
means error. If so, may I suggest not_shadowed_by
as a possible name? Alternatively, invert the sense: name it is_shadowed_by
.
I'd also like some comments that explain the logic here. Here is my attempt to figure it out. =) I think some examples are helpful, too, so I'll try to supply some.
This function defines the shadowing rules across macro invocations. The idea here is that if a macro definition defines a macro and then invokes it, we should resolve to the macro defined locally. So, for example, the following macro resolves just fine:
macro gen_inner_invoc() {
macro m() {} // B
m!(); // OK -- resolves to B
}
This is true even if that macro is defined (or invoked) in a context that already contains a macro m
:
macro m() { } // macro def'n A, shadowed by B above
maco gen_inner_invoc() { /* .. as above .. */ }
gen_inner_invoc!();
The general rule is that a macro B will shadow some "earlier" macro A if:
- B is defined in a "subinvocation" of the one that defined A (as above)
- B is XX (see my questions below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think "shadowed by"/"not shadowed by" is not the right way to think about it, that also leads to confusion here and in #53778 (comment).
Macro from inner scope always shadows a macro from outer scope:
// Fully expanded
macro m() {} // Outer
{
macro m(); // Inner, always shadows outer
}
the question is whether an instance of shadowing is "good" or "bad", which is determined entirely by their relative expansion order (earlier/later relations on expansions).
I hoped to convey that in the comments to may_appear_after
, but probably not as successfully as I thought.
I'll try to reread this comment and #53778 (comment) later this evening and say something more specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I.. well I'm not sure I see but I may be starting to see. =) I sort of had the sense I was thinking about it wrong. I will try to revisit this with this paragraph in mind.
let certainly_before_other_or_simultaneously = | ||
other_parent_expansion.is_descendant_of(self_parent_expansion); | ||
let certainly_before_invoc_or_simultaneously = | ||
invoc_parent_expansion.is_descendant_of(self_parent_expansion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand this. I think this is saying that the self
would legally be shadowed by any resolution that happens in "a child" macro.. but is that right? I'm probably missing some bit of context here. I guess I'm imagining that this would apply in a case like:
macro m { .. }
macro foo {
() => m!()
}
foo!() { }
Here, the invocation foo!
is a "sub-expansion" of the one that defined m
-- but this seems to suggest that there hence m
the decision to use m
cannot be shadowed. I guess that means that (e.g.) we have "definition 1" here?
macro m { .. } // definition 1
macro gen_m { () => macro m { ... } }
gen_m!(); // generates definition 2, possibly later
macro foo {
() => m!() // resolves to definition 1
}
foo!() { }
I see that we are time sensitive here. If needed, I think we could go ahead and land this PR, but I would really like it if we can start to write down in a clearer way (apart from the source, ideally) our "name resolution model". |
@nikomatsakis I'm still sure what this PR does is no worse than what the compiler was doing before it, and is also better in some sense. I need one more evening. |
First part of my notes: Preface 1, The end result of macro expansion.
Preface 2, Two stages.Each macro call Initial resolutionHappens when the crate is incomplete, some items are missing (not produced by expansions yet). Validating resolutionHappens when everything is expanded, the crate is complete and all items are in place. Looking at the initial resolution in more detailSo, we have just resolved some macro invocation to some macro definition (maybe even incorrect), what does that mean? ... |
☔ The latest upstream changes (presumably #53410) made this pull request unmergeable. Please resolve the merge conflicts. |
src/librustc_resolve/lib.rs
Outdated
}) | ||
} else if is_glob1 { | ||
} else if b1.is_glob_import() { | ||
Some(format!("consider adding an explicit import of `{}` to disambiguate", name)) | ||
} else { | ||
None | ||
}; | ||
|
||
let mut err = struct_span_err!(self.session, span, E0659, "`{}` is ambiguous", name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add a span_label
pointing at span
, maybe duplicating the text from the error?
I am a bit surprised to here that shadowing is not the right way to think about it. I think that this problem arises, perhaps, because we are not considering a "rich enough" view of the expanded source. Perhaps if we included the "trace" of the macros that were expanded in our result, we could define shadowing rules? I basically expect that we ought to be able to show a fully expanded source and say that the error occurs because some macro invocation has two candidates and neither of them shadows one another. Then we can define the rule that lets a macro invocation shadow another as some specific invocation site. It seems like the answer is:
I say that something is an "ancestor" if, in the tree of invocations, it is an ancestor -- in other words, if some text A was expanded to include B, then A is an ancestor of B. Also, B is an ancestor of itself. A "strict ancestor" is not. Applying that to this test you wrote: fn check1() {
macro m() {} // candidate A
{
#[rustc_transparent_macro]
macro gen_gen_inner_invoc() {
gen_inner!(); // generates candidate B
m!(); //~ ERROR `m` is ambiguous
}
gen_gen_inner_invoc!();
}
} I believe that this fully expands to: macro m { } // candidate A
{
macro gen_gen_inner_invoc() { /* definition not relevant */ }
gen_gen_inner_invoc!() as {
gen_inner!() as {
macro m { .. } // candidate B
}
m!() // ERROR
}
} Here we have an error because candidate B is not an ancestor of the invocation. In contrast, in fn check5() {
macro m() {}
{
#[rustc_transparent_macro]
macro gen_inner_invoc() {
macro m() {}
m!(); // OK
}
gen_inner_invoc!();
}
} we fully expand to macro m { } // candidate A
{
macro gen_gen_inner_invoc() { /* definition not relevant */ }
gen_inner_invoc!() as {
macro m { .. } // candidate B
m!() // ERROR
}
} Here it works out because both A and B are ancestors of the invocation site, and A is a strict ancestor of B. In this example, my rule doesn't work, because I didn't account for blocking scoping: fn check10() {
macro m() {}
{
macro m() {}
gen_invoc!(); // OK
}
} but I think you can extend my "tree" notion to include not just macro invocations but also block scoping, and then it works fine here. This example fn check13() {
macro m() {}
{
gen_inner!();
#[rustc_transparent_macro]
macro gen_invoc() { m!() } //~ ERROR `m` is ambiguous
gen_invoc!();
}
} is basically the same as (I realize now I didn't define how a macro becomes visible, but that is basically the same as any other amount of lexical scoping, except that we traverse through macro invocations.) Does this description of the rule make sense to you @petrochenkov ? |
996f44c
to
2dce377
Compare
@nikomatsakis |
@bors r=nikomatsakis p=1 |
📌 Commit 2dce377 has been approved by |
💡 This pull request was already approved, no need to approve it again.
|
📌 Commit 2dce377 has been approved by |
resolve: Relax shadowing restrictions on macro-expanded macros Previously any macro-expanded macros weren't allowed to shadow macros from outer scopes. Now only "more macro-expanded" macros cannot shadow "less macro-expanded" macros. See comments to `fn may_appear_after` and added tests for more details and examples. The functional changes are a21f6f588fc28c97533130ae44a6957b579ab58c and 46dd365ce9ca0a6b8653849b80267763c542842a, other commits are refactorings.
☀️ Test successful - status-appveyor, status-travis |
Previously any macro-expanded macros weren't allowed to shadow macros from outer scopes.
Now only "more macro-expanded" macros cannot shadow "less macro-expanded" macros.
See comments to
fn may_appear_after
and added tests for more details and examples.The functional changes are a21f6f588fc28c97533130ae44a6957b579ab58c and 46dd365ce9ca0a6b8653849b80267763c542842a, other commits are refactorings.