about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNathaniel McCallum <nathaniel@mccallum.life>2025-08-10 01:38:17 -0400
committerNathaniel McCallum <nathaniel@mccallum.life>2025-08-10 02:21:11 -0400
commit6b8a812aafaad6b0edd04d12569fe43ee1e69eed (patch)
tree3b8e1783bc833916e7fd78de1181dd2c28cb59ce /src
parenteca31528356d9721d9f4dc727da1bda20dfee932 (diff)
downloadrust-6b8a812aafaad6b0edd04d12569fe43ee1e69eed.tar.gz
rust-6b8a812aafaad6b0edd04d12569fe43ee1e69eed.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs11
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs6
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast49
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast23
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs2
6 files changed, 73 insertions, 19 deletions
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
index cb1b59f6497..d419817e5cd 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
@@ -182,12 +182,6 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
             );
             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() {
                 m.abandon(p);
@@ -219,8 +213,13 @@ fn path_type_bound(p: &mut Parser<'_>) -> Result<(), ()> {
     // test async_trait_bound
     // fn async_foo(_: impl async Fn(&i32)) {}
     p.eat(T![async]);
+    // test question_for_type_trait_bound
+    // fn f<T>() where T: for<> ?Sized {}
     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) {
         types::path_type_bounds(p, false);
         // test_err type_bounds_macro_call_recovery
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
index c642e1a3354..a3cfe64e6e7 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
@@ -796,6 +796,12 @@ mod err {
     #[test]
     fn impl_type() { run_and_expect_errors("test_data/parser/inline/err/impl_type.rs"); }
     #[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() {
         run_and_expect_errors("test_data/parser/inline/err/let_else_right_curly_brace.rs");
     }
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast
new file mode 100644
index 00000000000..b060ee81d61
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast
@@ -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
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs
new file mode 100644
index 00000000000..f80dd90d446
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs
@@ -0,0 +1 @@
+fn f<T>() where T: ?for<> Sized {}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
index cb296153c8f..69db1aee2c5 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
@@ -27,19 +27,18 @@ SOURCE_FILE
         WHITESPACE " "
         TYPE_BOUND_LIST
           TYPE_BOUND
+            FOR_BINDER
+              FOR_KW "for"
+              GENERIC_PARAM_LIST
+                L_ANGLE "<"
+                R_ANGLE ">"
+            WHITESPACE " "
             QUESTION "?"
-            FOR_TYPE
-              FOR_BINDER
-                FOR_KW "for"
-                GENERIC_PARAM_LIST
-                  L_ANGLE "<"
-                  R_ANGLE ">"
-              WHITESPACE " "
-              PATH_TYPE
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Sized"
+            PATH_TYPE
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "Sized"
     WHITESPACE " "
     BLOCK_EXPR
       STMT_LIST
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs
index f80dd90d446..96353df3b9b 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs
@@ -1 +1 @@
-fn f<T>() where T: ?for<> Sized {}
+fn f<T>() where T: for<> ?Sized {}