parser: fix parsing of trait bound polarity and for-binders

The rustc AST allows both `for<>` binders and `?` polarity
modifiers in trait bounds, but they are parsed in a specific
order and validated for correctness:

  1. `for<>` binder is parsed first.
  2. Polarity modifiers (`?`, `!`) are parsed second.
  3. The parser validates that binders and polarity modifiers
     do not conflict:

```rust
if let Some(binder_span) = binder_span {
    match modifiers.polarity {
        BoundPolarity::Maybe(polarity_span) => {
            // Error: "for<...> binder not allowed with ? polarity"
        }
    }
}
```

This implies:

- `for<> ?Sized` → Valid syntax. Invalid semantics.
- `?for<> Sized` → Invalid syntax.

However, rust-analyzer incorrectly had special-case logic that
allowed `?for<>` as valid syntax. This fix removes that incorrect
special case, making rust-analyzer reject `?for<> Sized` as a
syntax error, matching rustc behavior.

This has caused confusion in other crates (such as syn) which
rely on these files to implement correct syntax evaluation.
This commit is contained in:
Nathaniel McCallum 2025-08-10 01:38:17 -04:00
parent 31db5b5be9
commit 943b42f743
6 changed files with 73 additions and 19 deletions

View file

@ -182,12 +182,6 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
); );
m.complete(p, USE_BOUND_GENERIC_ARGS); m.complete(p, USE_BOUND_GENERIC_ARGS);
} }
T![?] if p.nth_at(1, T![for]) => {
// test question_for_type_trait_bound
// fn f<T>() where T: ?for<> Sized {}
p.bump_any();
types::for_type(p, false)
}
_ => { _ => {
if path_type_bound(p).is_err() { if path_type_bound(p).is_err() {
m.abandon(p); m.abandon(p);
@ -219,8 +213,13 @@ fn path_type_bound(p: &mut Parser<'_>) -> Result<(), ()> {
// test async_trait_bound // test async_trait_bound
// fn async_foo(_: impl async Fn(&i32)) {} // fn async_foo(_: impl async Fn(&i32)) {}
p.eat(T![async]); p.eat(T![async]);
// test question_for_type_trait_bound
// fn f<T>() where T: for<> ?Sized {}
p.eat(T![?]); p.eat(T![?]);
// test_err invalid_question_for_type_trait_bound
// fn f<T>() where T: ?for<> Sized {}
if paths::is_use_path_start(p) { if paths::is_use_path_start(p) {
types::path_type_bounds(p, false); types::path_type_bounds(p, false);
// test_err type_bounds_macro_call_recovery // test_err type_bounds_macro_call_recovery

View file

@ -796,6 +796,12 @@ mod err {
#[test] #[test]
fn impl_type() { run_and_expect_errors("test_data/parser/inline/err/impl_type.rs"); } fn impl_type() { run_and_expect_errors("test_data/parser/inline/err/impl_type.rs"); }
#[test] #[test]
fn invalid_question_for_type_trait_bound() {
run_and_expect_errors(
"test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs",
);
}
#[test]
fn let_else_right_curly_brace() { fn let_else_right_curly_brace() {
run_and_expect_errors("test_data/parser/inline/err/let_else_right_curly_brace.rs"); run_and_expect_errors("test_data/parser/inline/err/let_else_right_curly_brace.rs");
} }

View file

@ -0,0 +1,49 @@
SOURCE_FILE
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "f"
GENERIC_PARAM_LIST
L_ANGLE "<"
TYPE_PARAM
NAME
IDENT "T"
R_ANGLE ">"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
WHERE_CLAUSE
WHERE_KW "where"
WHITESPACE " "
WHERE_PRED
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "T"
COLON ":"
WHITESPACE " "
TYPE_BOUND_LIST
QUESTION "?"
WHERE_PRED
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
R_ANGLE ">"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Sized"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
R_CURLY "}"
WHITESPACE "\n"
error 20: expected comma
error 31: expected colon

View file

@ -0,0 +1 @@
fn f<T>() where T: ?for<> Sized {}

View file

@ -27,19 +27,18 @@ SOURCE_FILE
WHITESPACE " " WHITESPACE " "
TYPE_BOUND_LIST TYPE_BOUND_LIST
TYPE_BOUND TYPE_BOUND
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
R_ANGLE ">"
WHITESPACE " "
QUESTION "?" QUESTION "?"
FOR_TYPE PATH_TYPE
FOR_BINDER PATH
FOR_KW "for" PATH_SEGMENT
GENERIC_PARAM_LIST NAME_REF
L_ANGLE "<" IDENT "Sized"
R_ANGLE ">"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Sized"
WHITESPACE " " WHITESPACE " "
BLOCK_EXPR BLOCK_EXPR
STMT_LIST STMT_LIST

View file

@ -1 +1 @@
fn f<T>() where T: ?for<> Sized {} fn f<T>() where T: for<> ?Sized {}