-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Make break and continue hygienic #12338
Conversation
@@ -5177,7 +5177,7 @@ impl Resolver { | |||
let rib = label_ribs.get()[label_ribs.get().len() - | |||
1]; | |||
let mut bindings = rib.bindings.borrow_mut(); | |||
bindings.get().insert(label.name, def_like); | |||
bindings.get().insert(mtwt_resolve(label), def_like); |
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.
One way to check that this is doing something sensible is to write something like
let renamed = mtwt_resolve(label);
info!("ExprLoop label: {:?} -> {:?}", label, renamed);
bindings.get().insert(renamed, def_like);
and then run the compiler on the test cases like RUST_LOG=rustc::middle::resolve=info .../bin/rustc testcase.rs
to check if it's being renamed or not.
(I suggest using info!
rather than debug!
for now so that you can avoid printing all the other debug!
statements in this module and focus on just what you're interested in.)
fn main() { | ||
'x: for _ in range(0, 1) { | ||
// this 'x should refer to the outer loop, lexically | ||
run_once!(continue 'x); |
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.
Interesting output from info!
inside rustc::middle::resolve
. I saw some phantom continue
presumably because the same continue
gets visited and renamed twice here and inside the macro above:
ExprLoop label: syntax::ast::Ident{name: 59u32, ctxt: 22u32} -> 258u32
ExprBreak/ExprAgain label: syntax::ast::Ident{name: 59u32, ctxt: 22u32} -> 258u32
ExprLoop label: syntax::ast::Ident{name: 59u32, ctxt: 26u32} -> 260u32
ExprBreak/ExprAgain label: syntax::ast::Ident{name: 59u32, ctxt: 26u32} -> 260u32
ExprBreak/ExprAgain label: syntax::ast::Ident{name: 59u32, ctxt: 26u32} -> 260u32
Judging from the same debug output, it seems that SyntaxContext
is more important here. Where is label assigned a SyntaxContext
?
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.
Hm... it should only be visited once, in place in the expansion (since expansion runs and finishes before resolve starts). I guess resolve might be doing multiple passes...
But it certainly seems to be distinguishing between labels with the same name.
(I think the SyntaxContext
is what is set by the ident renaming step of macro expansion.)
Nifty! |
I don't personally understand the implementation of hygiene that much, but this seems pretty reasonable. Is this ready for merging? |
@@ -166,7 +180,7 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr { | |||
// `None => break ['<ident>];` | |||
let none_arm = { | |||
// FIXME #6993: this map goes away: |
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.
Should this be removed?
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.
Indeed.
No, it doesn't work yet. The label seems to be renamed twice by enclosing lexical scope and macro so the net effect is that |
cc #10607. It looks like renaming can be costly. |
Let's at least get tests as comprehensive as possible first. Right now there are five of them, three run-pass and two compile-fail; all don't work yet. |
I'm not sure I'm reading your code correctly, but it looks like you might be performing renaming too eagerly, adding it to the pending renames and also applying it directly. This could explain your renaming-twice issue, or it could VERY WELL BE that I'm just misreading your code. In any case: great to see people working on hygiene for loop labels! |
The current renaming approach does work for one case and one case only: macro_rules! foo {
() => { break 'x; }
}
pub fn main() {
'x: for _ in range(0,1) { foo!() } //~ ERROR use of undeclared label `x`
} Although the error message is not very accurate; it could be reported as: macro_rules! foo {
() => { break 'x; } //~ ERROR use of undeclared label `x`
}
pub fn main() {
'x: for _ in range(0,1) { foo!() }
} |
I'm not sure the hygienic label can work using renaming given the current macro expansion mechanism. Consider the following: macro_rules! loop_x {
($e: expr) => {
// $e shouldn't be able to interact with this 'x
'x: loop { $e }
}
}
fn main() {
'x: loop {
// this 'x should refer to the outer loop, lexically
loop_x!(break 'x);
assert!(false);
}
} As far as hygiene is concerned, there are three types of labels:
To work hygienically, type 1 and type 2 labels are lexically in one group and type 3 the other. If they clash, one group or the other need to be renamed. The renaming can only be performed on a fully realized AST tree. The problem is, given the current macro expansion mechanism, type 2 and type 3 labels are parsed together (in the macro expand function). After an AST has been constructed, they can no longer be distinguished from each other. |
No, I believe you misunderstand how renaming works. The renaming is applied to every hygienically-treated identifier, and it definitely does not require a fully-expanded AST. Without getting into the really interesting details, in this example the outer 'x would get a fresh rename that applies to the macro argument before the expansion of the macro occurs. Things can get really complicated, but I believe what you're looking at here is one of the simpler cases. Again, if I understand you, you can try this using simple let-bound variables. Is there something that would make labels different? |
But the outer |
Yes, my comment about renaming being performed on a fully expanded AST tree was totally wrong; it can be done in the process of expanding AST tree. The type 2 and type 3 labels being parsed together is till true so that I couldn't find a way to rename outer |
When you see the outer 'x, it goes on a list of pending renames that gets applied to the argument to the macro. Right? |
I think I figured it out. This fold.rs#L374 holds the key. After being tokenized and before being parsed, a label is represented as But #7743 blocked the renaming of lifetimes. I'm fixing that first right now. |
This needs a rebase (feel free to have it rebased on top of your branch for #12451; GitHub normally automatically copes pretty well when one lands before the other). |
Will do a rebase. I'm fighting with lifetime resolving now since I turned on |
The new tests should pass now. Although it works, to be honest I'm not entirely sure about this patch. But let's see what you guys have to say about it. In the meantime, I need to run full test suite locally and add even more tests. |
Makes labelled loops hygiene by performing renaming of the labels defined in e.g. `'x: loop { ... }` and then used in break and continue statements within loop body so that they act hygienically when used with macros. Closes rust-lang#12262.
r? |
I'd like to try :P |
Makes labelled loops hygiene by performing renaming of the labels defined in e.g. `'x: loop { ... }` and then used in break and continue statements within loop body so that they act hygienically when used with macros. Closes #12262.
fix: Fix broken async callback in join lines Fixes rust-lang#12338.
[`infinite_loops`]: fix incorrect suggestions on async functions/closures closes: rust-lang#12338 I intend to fix this in rust-lang#12421 but got distracted by some other problems in the same lint, delaying the process of closing the actual issue. So here's a separated PR that only focus on the issue and nothing else. --- changelog: [`infinite_loops`]: fix suggestion error on async functions/closures
Makes labelled loops hygiene by performing renaming of the labels defined in e.g.
'x: loop { ... }
and then used in break and continue statements within loop body so that they act hygienically when used with macros.Closes #12262.