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

Fix, document, and test parser and pretty-printer edge cases related to braced macro calls #119427

Merged
merged 17 commits into from May 12, 2024

Conversation

dtolnay
Copy link
Member

@dtolnay dtolnay commented Dec 30, 2023

Review note: this is a deceptively small PR because it comes with 145 lines of docs and 196 lines of tests, and only 25 lines of compiler code changed. However, I recommend reviewing it 1 commit at a time because much of the effect of the code changes is non-local i.e. affecting code that is not visible in the final state of the PR. I have paid attention that reviewing the PR one commit at a time is as easy as I can make it. All of the code you need to know about is touched in those commits, even if some of those changes disappear by the end of the stack.

This is a follow-up to #119105. One case that is not relevant to -Zunpretty=expanded, but which came up as I'm porting #119105 and #118726 into syn's printer and prettyplease's printer where it is relevant, and is also relevant to rustc's stringify!, is statement boundaries in the vicinity of braced macro calls.

Rustc's AST pretty-printer produces invalid syntax for statements that begin with a braced macro call:

macro_rules! stringify_item {
    ($i:item) => {
        stringify!($i)
    };
}

macro_rules! repro {
    ($e:expr) => {
        stringify_item!(fn main() { $e + 1; })
    };
}

fn main() {
    println!("{}", repro!(m! {}));
}

Before this PR: output is not valid Rust syntax.

fn main() { m! {} + 1; }
error: leading `+` is not supported
 --> <anon>:1:19
  |
1 | fn main() { m! {} + 1; }
  |                   ^ unexpected `+`
  |
help: try removing the `+`
  |
1 - fn main() { m! {} + 1; }
1 + fn main() { m! {}  1; }
  |

After this PR: valid syntax.

fn main() { (m! {}) + 1; }

@rustbot
Copy link
Collaborator

rustbot commented Dec 30, 2023

r? @WaffleLapkin

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 30, 2023
@dtolnay dtolnay added the A-pretty Area: Pretty printing. label Dec 30, 2023
@rust-log-analyzer

This comment has been minimized.

@dtolnay dtolnay force-pushed the maccall branch 2 times, most recently from 584de16 to 48ac127 Compare December 30, 2023 03:38
@dtolnay dtolnay added the A-parser Area: The parsing of Rust source code to an AST. label Dec 30, 2023
Copy link
Member

@WaffleLapkin WaffleLapkin left a comment

Choose a reason for hiding this comment

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

I think I agree with the idea, but I'm not sure about the implementation. Is there a way to make this nicer?

| TryBlock(..)
| ConstBlock(..) => false,

MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,
Copy link
Member

Choose a reason for hiding this comment

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

why is this needed, if all the call sites are not calling this if e ≈ MacCall?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the commit that adds this MacCall case inside expr_requires_semi_to_be_stmt, I change all the call sites to preserve their pre-existing behavior. So this case starts out as not reachable. But after that, one at a time for each call site, I update each call site that should be reaching this, together with adding documentation and tests for that call site.

tests/ui/macros/stringify.rs Outdated Show resolved Hide resolved
compiler/rustc_parse/src/parser/expr.rs Outdated Show resolved Hide resolved
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 2, 2024
@bors
Copy link
Contributor

bors commented Jan 19, 2024

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

@WaffleLapkin
Copy link
Member

r? compiler

@rustbot rustbot assigned b-naber and unassigned WaffleLapkin Jan 20, 2024
@fmease
Copy link
Member

fmease commented Mar 6, 2024

#120690
r? compiler

@rustbot rustbot assigned michaelwoerister and unassigned b-naber Mar 6, 2024
@dtolnay
Copy link
Member Author

dtolnay commented Mar 6, 2024

This is waiting on me to incorporate Waffle's insightful feedback, and to rebase.

jieyouxu added a commit to jieyouxu/rust that referenced this pull request Apr 20, 2024
Give a name to each distinct manipulation of pretty-printer FixupContext

There are only 7 distinct ways that the AST pretty-printer interacts with FixupContext: 3 constructors (including Default), 2 transformations, and 2 queries.

This PR turns these into associated functions which can be documented with examples.

This PR unblocks rust-lang#119427 (comment). In order to improve the pretty-printer's behavior regarding parenthesization of braced macro calls in match arms, which have different grammar than macro calls in statements, FixupContext needs to be extended with 2 new fields. In the previous approach, that would be onerous. In the new approach, all it entails is 1 new constructor (`FixupContext::new_match_arm()`).
jieyouxu added a commit to jieyouxu/rust that referenced this pull request Apr 20, 2024
Give a name to each distinct manipulation of pretty-printer FixupContext

There are only 7 distinct ways that the AST pretty-printer interacts with FixupContext: 3 constructors (including Default), 2 transformations, and 2 queries.

This PR turns these into associated functions which can be documented with examples.

This PR unblocks rust-lang#119427 (comment). In order to improve the pretty-printer's behavior regarding parenthesization of braced macro calls in match arms, which have different grammar than macro calls in statements, FixupContext needs to be extended with 2 new fields. In the previous approach, that would be onerous. In the new approach, all it entails is 1 new constructor (`FixupContext::new_match_arm()`).
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 21, 2024
Rollup merge of rust-lang#124191 - dtolnay:fixup, r=compiler-errors

Give a name to each distinct manipulation of pretty-printer FixupContext

There are only 7 distinct ways that the AST pretty-printer interacts with FixupContext: 3 constructors (including Default), 2 transformations, and 2 queries.

This PR turns these into associated functions which can be documented with examples.

This PR unblocks rust-lang#119427 (comment). In order to improve the pretty-printer's behavior regarding parenthesization of braced macro calls in match arms, which have different grammar than macro calls in statements, FixupContext needs to be extended with 2 new fields. In the previous approach, that would be onerous. In the new approach, all it entails is 1 new constructor (`FixupContext::new_match_arm()`).
@dtolnay
Copy link
Member Author

dtolnay commented Apr 21, 2024

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Apr 21, 2024
@michaelwoerister
Copy link
Member

Thanks for the PR, @dtolnay, looks great! However, after looking through all the commits, I think I don't understand enough about the parsing subtleties involved here to review this.

r? parser

…lse'

The change to the test is a little goofy because the compiler was
guessing "correctly" before that `falsy! {}` is the condition as opposed
to the else body. But I believe this change is fundamentally correct.
Braced macro invocations in statement position are most often item-like
(`thread_local! {...}`) as opposed to parenthesized macro invocations
which are condition-like (`cfg!(...)`).
It is impossible for expr here to be a braced macro call. Expr comes
from `parse_stmt_without_recovery`, in which macro calls are parsed by
`parse_stmt_mac`. See this part:

    let kind = if (style == MacStmtStyle::Braces
        && self.token != token::Dot
        && self.token != token::Question)
        || self.token == token::Semi
        || self.token == token::Eof
    {
        StmtKind::MacCall(P(MacCallStmt { mac, style, attrs, tokens: None }))
    } else {
        // Since none of the above applied, this is an expression statement macro.
        let e = self.mk_expr(lo.to(hi), ExprKind::MacCall(mac));
        let e = self.maybe_recover_from_bad_qpath(e)?;
        let e = self.parse_expr_dot_or_call_with(e, lo, attrs)?;
        let e = self.parse_expr_assoc_with(
            0,
            LhsExpr::AlreadyParsed { expr: e, starts_statement: false },
        )?;
        StmtKind::Expr(e)
    };

A braced macro call at the head of a statement is always either extended
into ExprKind::Field / MethodCall / Await / Try / Binary, or else
returned as StmtKind::MacCall. We can never get a StmtKind::Expr
containing ExprKind::MacCall containing brace delimiter.
@dtolnay
Copy link
Member Author

dtolnay commented May 11, 2024

compiler/rustc_ast_pretty/src/pprust/state.rs Outdated Show resolved Hide resolved
compiler/rustc_lint/src/unused.rs Outdated Show resolved Hide resolved
compiler/rustc_parse/src/parser/expr.rs Show resolved Hide resolved
self.restrictions.contains(Restrictions::STMT_EXPR)
&& !classify::expr_requires_semi_to_be_stmt(e)
&& !classify::expr_requires_comma_to_be_match_arm(e)
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 maybe my only nit in the PR: that expr_is_complete uses expr_requires_comma_to_be_match_arm "feels" a bit less self-descriptive than it could be, since we use expr_is_complete for things like parse_expr_dot_or_call_with_ which doesn't really have to do with match arms.

Not really sure how to make this actionable though, since I'm not sure if a rename seems tough. AFAICT this just preserves the old behavior, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, this is preserving old behavior. The exact same behavior that used to be incorrectly called expr_requires_semi_to_be_stmt is now called expr_requires_comma_to_be_match_arm.

Regarding naming, one possibility is that instead of distinguishing a "commas in match arms" and "semicolons in statements" case (and the implicit 3rd case, expressions that are neither statement nor match arm, such as a function arg), we could rename things along the following lines:

  • "parse the longest possible expression"
  • "parse an expression using earlier boundary rule" (for a match arm, range, or whatever else looks at expr_is_complete)
  • "parse an expression until the earliest possible boundary" (for a statement)

Another possibility is to omit this specific commit ("Add classify::expr_requires_comma_to_be_match_arm") and keep using match e { MacCall(_) => …, _ => expr_requires_semi_to_be_stmt(e) } in the cases where expr_requires_semi_to_be_stmt is not the desired behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

One more option — instead of having what's currently in the PR:

// compiler/rustc_ast/src/util/classify.rs

pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
    match &e.kind {
        If(..)
        | Match(..)
        | Block(..)
        | While(..)
        | Loop(..)
        | ForLoop { .. }
        | TryBlock(..)
        | ConstBlock(..) => false,

        MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,

        _ => true,
    }
}

pub fn expr_requires_comma_to_be_match_arm(e: &ast::Expr) -> bool {
    match &e.kind {
        MacCall(_) => true,
        _ => expr_requires_semi_to_be_stmt(e),
    }
}

we could have:

pub fn expr_is_complete(e: &ast::Expr) -> bool {
    // this is negative of the old "expr_requires_semi_to_be_stmt"
    match &e.kind {
        If(..)
        | Match(..)
        | Block(..)
        | While(..)
        | Loop(..)
        | ForLoop { .. }
        | TryBlock(..)
        | ConstBlock(..) => true,

        MacCall(_) | _ => false,
    }
}

pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
    match &e.kind {
        MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,
        _ => !expr_is_complete(e),
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

I prefer either the final option or the renaming you suggested first above. I'll leave it up to you. Otherwise this PR looks good to me.

Copy link
Member Author

@dtolnay dtolnay May 12, 2024

Choose a reason for hiding this comment

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

I removed 5d5954c and added 10227ea.

Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

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

🆒

@compiler-errors
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented May 12, 2024

📌 Commit a9ec581 has been approved by compiler-errors

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 12, 2024
@rust-log-analyzer

This comment has been minimized.

@dtolnay
Copy link
Member Author

dtolnay commented May 12, 2024

@bors r=compiler-errors

@bors
Copy link
Contributor

bors commented May 12, 2024

📌 Commit 78c8dc1 has been approved by compiler-errors

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented May 12, 2024

⌛ Testing commit 78c8dc1 with merge 8cc6f34...

@bors
Copy link
Contributor

bors commented May 12, 2024

☀️ Test successful - checks-actions
Approved by: compiler-errors
Pushing 8cc6f34 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label May 12, 2024
@bors bors merged commit 8cc6f34 into rust-lang:master May 12, 2024
7 checks passed
@rustbot rustbot added this to the 1.80.0 milestone May 12, 2024
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (8cc6f34): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
4.1% [4.1%, 4.1%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-2.5% [-2.5%, -2.5%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.8% [-2.5%, 4.1%] 2

Cycles

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
3.8% [2.8%, 5.6%] 3
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 675.636s -> 678.17s (0.38%)
Artifact size: 315.95 MiB -> 315.89 MiB (-0.02%)

@dtolnay dtolnay deleted the maccall branch May 12, 2024 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-parser Area: The parsing of Rust source code to an AST. A-pretty Area: Pretty printing. merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet