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

Simplify the macro hygiene algorithm #34570

Merged
merged 7 commits into from
Jul 15, 2016
Merged

Conversation

jseyfried
Copy link
Contributor

@jseyfried jseyfried commented Jun 30, 2016

This PR removes renaming from the hygiene algorithm and treats differently marked identifiers as unequal.

This change makes the scope of identifiers in macro_rules! items empty. That is, identifiers in macro_rules! definitions do not inherit any semantics from the macro_rules!'s scope.

Since macro_rules! macros are items, the scope of their identifiers "should" be the same as that of other items; in particular, the scope should contain only items. Since all items are unhygienic today, this would mean the scope should be empty.

However, the scope of an identifier in a macro_rules! statement today is the scope that the identifier would have if it replaced the macro_rules! (excluding anything unhygienic, i.e. locals only).

To continue to support this, this PR tracks the scope of each macro_rules! and uses it in resolve to ensure that an identifier expanded from a macro_rules! gets a chance to resolve to the locals in the macro_rules!'s scope.

This PR is a pure refactoring. After this PR,

  • syntax::ext::expand is much simpler.
  • We can expand macros in any order without causing problems for hygiene (needed for macro modularization).
  • We can deprecate or remove today's macro_rules! scope easily.
  • Expansion performance improves by 25%, post-expansion memory usage decreases by ~5%.
  • Expanding a block is no longer quadratic in the number of let statements (fixes Macro expansion is quadratic in the number of let statements. #10607).

r? @nrc

@@ -46,7 +45,7 @@ pub struct SyntaxContext(pub u32);
/// An identifier contains a Name (index into the interner
/// table) and a SyntaxContext to track renaming and
/// macro expansion per Flatt et al., "Macros That Work Together"
#[derive(Clone, Copy, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
Copy link
Contributor

Choose a reason for hiding this comment

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

So, operator == and hash for Ident did unhygienic comparisons and now they do hygienic comparisons.
I'm not sure what unintended consequences it may have.
I remember derived implementations of PartialEq were used in couple of places for unhygienic comparisons of larger parts of AST including identifiers as their parts (maybe even expressions?), now the meaning of those comparisons changed.
Have you looked at where == for Idents was previously used and how the meaning of that code changed?

Copy link
Contributor Author

@jseyfried jseyfried Jul 3, 2016

Choose a reason for hiding this comment

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

We used to panic! when using == to compare two identifiers with different syntax contexts, so if we didn't panic! before then the behavior won't change.

Copy link
Contributor

Choose a reason for hiding this comment

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

This probably doesn't matter much because the comparisons that changed the meaning could only result in panic before, but still worth checking and maybe fixing later, it's probably not a good idea to compare AST pieces with derived PartialEq anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed -- I'll look into it but I don't think it blocks this PR.

@petrochenkov
Copy link
Contributor

Could you add a test with >1 levels of macro_rules and "captured" local variables, something like

fn main() {
    let a = 10;
    macro_rules! m {
        () => (
            let b = 11;
            macro_rules! n {
                () => {
                    (a, b)
                }
            }
        )
    }
    m!();
    let c = n!();
}

@petrochenkov
Copy link
Contributor

This is awesome. The last autumn I tried to figure out how this combination of expand and mtwt works in detail but gave up and now it turns out that most of that complexity exists to support one mis-feature!
I reviewed this, but I can't really be sure that b0c7064 correctly replaces the previous scheme, so I'll pass this responsibility to @nrc.

@jseyfried
Copy link
Contributor Author

jseyfried commented Jul 3, 2016

@petrochenkov The tests that I had written for this PR landed in #34374 -- I think you'll like them :)

@nrc
Copy link
Member

nrc commented Jul 4, 2016

Hi, I've been looking at this and I had some questions about it, I wanted to do some experimentation, but for some reason I couldn't get this branch to build. I think it is unrelated to the patch here and some weirdness thanks to LLVM (maybe due to an LLVM upgrade). Anyway, thanks for the PR and sorry for the delay in review, but I'm on it just as soon as I fix my build problem.

@jseyfried
Copy link
Contributor Author

@nrc no problem, I've also been having trouble building rustc recently.

@durka
Copy link
Contributor

durka commented Jul 6, 2016

I built this and verified that it works on unborrow, my personal hygiene stress test :)

@petrochenkov
Copy link
Contributor

cc @jbclements just in case

@jbclements
Copy link
Contributor

sigh... @nrc , any progress on sets of scopes? Honestly, the whole approach of doing macro expansion after into ASTs seems fundamentally awkward, if not completely broken.

@bors
Copy link
Contributor

bors commented Jul 10, 2016

☔ The latest upstream changes (presumably #34365) made this pull request unmergeable. Please resolve the merge conflicts.

@@ -171,15 +104,24 @@ pub fn outer_mark(ctxt: SyntaxContext) -> Mrk {
})
}

/// If `ident` is macro expanded, return the source ident from the macro definition
Copy link
Member

Choose a reason for hiding this comment

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

What does "source ident" mean for a procedural macro?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, this function returns None for idents expanded from procedural macros.

Ideally, I think it should return the unmarked ident along with a "null" macro definition mark (since procedural macros are not expanded into existence).

@jseyfried
Copy link
Contributor Author

Rebased

@nrc
Copy link
Member

nrc commented Jul 14, 2016

@bors: r+

TBH, I'm still pretty surprised that this works, but I think I've convinced myself that it does (at least for macro_rules) - everything I threw at it worked as expected and any theoretical issue I could think of would not actually be an issue because of some aspect of Rust. I do worry that there might be problems with procedural macros, but I couldn't come up with anything, so lets see if something materlialises.

@bors
Copy link
Contributor

bors commented Jul 14, 2016

📌 Commit 56c4ddf has been approved by nrc

@bors
Copy link
Contributor

bors commented Jul 14, 2016

⌛ Testing commit 56c4ddf with merge df8d15b...

bors added a commit that referenced this pull request Jul 14, 2016
Simplify the macro hygiene algorithm

This PR removes renaming from the hygiene algorithm and treats differently marked identifiers as unequal.

This change makes the scope of identifiers in `macro_rules!` items empty. That is, identifiers in `macro_rules!` definitions do not inherit any semantics from the `macro_rules!`'s scope.

Since `macro_rules!` macros are items, the scope of their identifiers "should" be the same as that of other items; in particular, the scope should contain only items. Since all items are unhygienic today, this would mean the scope should be empty.

However, the scope of an identifier in a `macro_rules!` statement today is the scope that the identifier would have if it replaced the `macro_rules!` (excluding anything unhygienic, i.e. locals only).

To continue to support this, this PR tracks the scope of each `macro_rules!` and uses it in `resolve` to ensure that an identifier expanded from a `macro_rules!` gets a chance to resolve to the locals in the `macro_rules!`'s scope.

This PR is a pure refactoring. After this PR,
 - `syntax::ext::expand` is much simpler.
 - We can expand macros in any order without causing problems for hygiene (needed for macro modularization).
 - We can deprecate or remove today's `macro_rules!` scope easily.
 - Expansion performance improves by 25%, post-expansion memory usage decreases by ~5%.
 - Expanding a block is no longer quadratic in the number of `let` statements (fixes #10607).

r? @nrc
@bors
Copy link
Contributor

bors commented Jul 14, 2016

💔 Test failed - auto-win-msvc-64-cargotest

@nrc
Copy link
Member

nrc commented Jul 15, 2016

@bors: retry

@jseyfried failure looks like it might be genuine

@jseyfried
Copy link
Contributor Author

jseyfried commented Jul 15, 2016

@bors r-
Failure is legit -- macro-expanded labels should have a chance to resolve in the macro_rules!'s scope just like macro-expanded locals.
i.e. MacroDefinition rib should be added to label ribs as well as value ribs. Will fix soon.

@jseyfried
Copy link
Contributor Author

jseyfried commented Jul 15, 2016

@nrc Fixed and added a regression test.

@eddyb
Copy link
Member

eddyb commented Jul 15, 2016

@bors r=nrc

@bors
Copy link
Contributor

bors commented Jul 15, 2016

📌 Commit c1a6ff2 has been approved by nrc

@bors
Copy link
Contributor

bors commented Jul 15, 2016

⌛ Testing commit c1a6ff2 with merge 1d99dec...

@bors
Copy link
Contributor

bors commented Jul 15, 2016

💔 Test failed - auto-win-gnu-64-opt

@jseyfried
Copy link
Contributor Author

@bors retry

@bors
Copy link
Contributor

bors commented Jul 15, 2016

⌛ Testing commit c1a6ff2 with merge 4db1874...

bors added a commit that referenced this pull request Jul 15, 2016
Simplify the macro hygiene algorithm

This PR removes renaming from the hygiene algorithm and treats differently marked identifiers as unequal.

This change makes the scope of identifiers in `macro_rules!` items empty. That is, identifiers in `macro_rules!` definitions do not inherit any semantics from the `macro_rules!`'s scope.

Since `macro_rules!` macros are items, the scope of their identifiers "should" be the same as that of other items; in particular, the scope should contain only items. Since all items are unhygienic today, this would mean the scope should be empty.

However, the scope of an identifier in a `macro_rules!` statement today is the scope that the identifier would have if it replaced the `macro_rules!` (excluding anything unhygienic, i.e. locals only).

To continue to support this, this PR tracks the scope of each `macro_rules!` and uses it in `resolve` to ensure that an identifier expanded from a `macro_rules!` gets a chance to resolve to the locals in the `macro_rules!`'s scope.

This PR is a pure refactoring. After this PR,
 - `syntax::ext::expand` is much simpler.
 - We can expand macros in any order without causing problems for hygiene (needed for macro modularization).
 - We can deprecate or remove today's `macro_rules!` scope easily.
 - Expansion performance improves by 25%, post-expansion memory usage decreases by ~5%.
 - Expanding a block is no longer quadratic in the number of `let` statements (fixes #10607).

r? @nrc
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.

Macro expansion is quadratic in the number of let statements.
7 participants