Unnamed repository; edit this file 'description' to name the repository.
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.
Nathaniel McCallum 8 months ago
parent 31db5b5 · commit 943b42f
-rw-r--r--crates/parser/src/grammar/generic_params.rs11
-rw-r--r--crates/parser/test_data/generated/runner.rs6
-rw-r--r--crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast49
-rw-r--r--crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs1
-rw-r--r--crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast23
-rw-r--r--crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs2
6 files changed, 73 insertions, 19 deletions
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index cb1b59f649..d419817e5c 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/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/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs
index c642e1a335..a3cfe64e6e 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/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/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast b/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rast
new file mode 100644
index 0000000000..b060ee81d6
--- /dev/null
+++ b/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/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs b/crates/parser/test_data/parser/inline/err/invalid_question_for_type_trait_bound.rs
new file mode 100644
index 0000000000..f80dd90d44
--- /dev/null
+++ b/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/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast b/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
index cb296153c8..69db1aee2c 100644
--- a/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rast
+++ b/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/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs b/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs
index f80dd90d44..96353df3b9 100644
--- a/crates/parser/test_data/parser/inline/ok/question_for_type_trait_bound.rs
+++ b/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 {}