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 10 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 {}