Skip to content

Bad diagnostic: "error: struct literal body without path" #82051

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

Closed
porglezomp opened this issue Feb 13, 2021 · 5 comments · Fixed by #106620
Closed

Bad diagnostic: "error: struct literal body without path" #82051

porglezomp opened this issue Feb 13, 2021 · 5 comments · Fixed by #106620
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-parser Area: The lexing & parsing of Rust source code to an AST C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-incorrect Diagnostics: A diagnostic that is giving misleading or incorrect information. D-invalid-suggestion Diagnostics: A structured suggestion resulting in incorrect code. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@porglezomp
Copy link
Contributor

porglezomp commented Feb 13, 2021

I tried to use a struct literal as part of a condition, and got a bad error message. Here's a reduced version that fails:

pub struct Example { a: i32 }

impl Example {
    fn is_pos(&self) -> bool { self.a > 0 }
}

fn one() -> i32 { 1 }

fn main() {
    if Example { a: one(), }.is_pos() {
        println!("Positive!");
    }
}

Playground link.

This gives these rather poor error messages which don't align with the actual problem:

Standard Error

   Compiling playground v0.0.1 (/playground)

error: struct literal body without path
  --> src/main.rs:10:16
   |
10 |     if Example { a: one(), }.is_pos() {
   |                ^^^^^^^^^^^^^
   |
help: you might have forgotten to add the struct literal inside the block
   |
10 |     if Example { SomeStruct { a: one(), } }.is_pos() {
   |                ^^^^^^^^^^^^               ^

error: expected one of `.`, `;`, `?`, `}`, or an operator, found `{`
  --> src/main.rs:10:39
   |
10 |     if Example { a: one(), }.is_pos() {
   |                                       ^ expected one of `.`, `;`, `?`, `}`, or an operator

(If you remove the trailing comma inside the struct literal, it only produces the second of these two errors.)

If I change the one() to a literal 1, then it gives a much more useful error message:

error: struct literals are not allowed here
  --> src/main.rs:10:8
   |
10 |     if Example { a: 1 }.is_pos() {
   |        ^^^^^^^^^^^^^^^^
   |
help: surround the struct literal with parentheses
   |
10 |     if (Example { a: 1 }).is_pos() {
   |        ^                ^

So something about having a function call in the struct initializer breaks the diagnostic.

Meta

Tested against the
rustc --version --verbose:

1.52.0-nightly (2021-02-12 3f5aee2d5241139d808f)
@porglezomp porglezomp added the C-bug Category: This is a bug. label Feb 13, 2021
@osa1
Copy link
Contributor

osa1 commented Feb 13, 2021

How are you trying this exactly? Your link doesn't generate the error you showed, it only generates the last error:

   Compiling playground v0.0.1 (/playground)
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `{`
  --> src/main.rs:10:38
   |
10 |     if Example { a: one() }.is_pos() {
   |                                      ^ expected one of `.`, `;`, `?`, `}`, or an operator

error: aborting due to previous error

error: could not compile `playground`

I tried with both stable and nightly channels of the playground link.

Still, the error message is much better when using 1 instead of one() in the field.

@porglezomp
Copy link
Contributor Author

porglezomp commented Feb 13, 2021

Oops, it looks like I messed things up while trying to clean things up. That error message was generated with a comma, here:

    if Example { a: one(), }.is_pos() {
                         ^

The code with the comma removed produces that shorter (still bad) error message! I'm going to edit the initial post to avoid confusing anyone else trying to reproduce it.

@jonas-schievink jonas-schievink added A-diagnostics Area: Messages for errors, warnings, and lints A-parser Area: The lexing & parsing of Rust source code to an AST labels Feb 13, 2021
@osa1
Copy link
Contributor

osa1 commented Feb 13, 2021

I don't know how to improve this error but let me explain the problem, perhaps someone else can figure it out.

When parsing an if expression condition (in parse_cond_expr) we update the parser state saying that we don't expect to parse a struct in this context, then call the expression parser as usual.

Then, after a long chain of calls, we see a path (Example in the repro) followed by a {, and call maybe_parse_struct_expr.

maybe_parse_struct_expr tries to parse a struct if one or both of these hold:

  • Structs allowed in this context (in our case this doesn't hold, remember that parse_cond_expr updated parsers state to disallow structs)
  • The tokens starting with { do not look like they could be parsed as a block.

With one() and 1 the second check returns different results. Code for the check: (slightly refactored for clarity)

    fn is_certainly_not_a_block(&self) -> bool {
        self.look_ahead(1, |t| t.is_ident())
            && (
                // `{ ident, ` cannot start a block.
                self.look_ahead(2, |t| t == &token::Comma)
                    || (self.look_ahead(2, |t| t == &token::Colon)
                        && (
                            // `{ ident: token, ` cannot start a block.
                            self.look_ahead(4, |t| t == &token::Comma) ||
                            // `{ ident: ` cannot start a block unless it's a type ascription `ident: Type`.
                            self.look_ahead(3, |t| !t.can_begin_type())
                        ))
            )
    }

The difference is self.look_ahead(3, |t| !t.can_begin_type()) returns false for a: one() but true for a: 1 (reminder that one() is actually [Id("one"), (, )] in token stream so we only check one and conclude that it could be a type).

As a result, parse_cond_expr parses Example { a: 1 } as a struct but generates "surround the struct with parens", but for Example { a: one() } it doesn't try to parse it as struct and doesn't make the suggestion.

(I don't know how exactly { a: one() } parsed in the problematic case)


One thing I tried is to use accurate struct parsing instead of is_certainly_not_a_block above, but that causes parse errors in the libraries. I didn't investigate further.

@henryboisdequin
Copy link
Contributor

@rustbot label +D-confusing +D-incorrect +D-invalid-suggestion +D-newcomer-roadblock

@rustbot rustbot added D-confusing Diagnostics: Confusing error or lint that should be reworked. D-incorrect Diagnostics: A diagnostic that is giving misleading or incorrect information. D-invalid-suggestion Diagnostics: A structured suggestion resulting in incorrect code. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. labels Feb 14, 2021
@estebank estebank added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Feb 14, 2021
@estebank
Copy link
Contributor

if Example { a: one() }.is_pos() { get's parsed as

if Example { // Example is some value. The parser doesn't have type information to know it isn't.
    a  // a block with a tail expression and no statements, `a` is an expression consisting of a single binding,
    :   // annotated with type ascription
    one() // ascribed to the "type" `one` which would *have* to be a trait with no type paremeters in parenthetical notation (like `FnMut()`)
}.is_pos() // a method call on whatever the result of the previous `if` is, which *has* to be `()` because it has no `else` arm
{ // oh no! a token that can follow an expression, you might have wanted to end a statement with `;`

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 11, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Detect struct literal needing parentheses

Fix rust-lang#82051.
@bors bors closed this as completed in 5311938 Jan 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-parser Area: The lexing & parsing of Rust source code to an AST C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-incorrect Diagnostics: A diagnostic that is giving misleading or incorrect information. D-invalid-suggestion Diagnostics: A structured suggestion resulting in incorrect code. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants