summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/parser')
-rw-r--r--src/tools/rust-analyzer/crates/parser/Cargo.toml17
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/event.rs13
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar.rs30
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs137
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs38
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs32
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs29
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs25
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/params.rs14
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs11
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/types.rs3
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lexed_str.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/output.rs61
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/parser.rs29
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/shortcuts.rs53
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/tests.rs6
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs4
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast3
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast38
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast18
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast211
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast7
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast19
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast77
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast33
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast43
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast35
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast42
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs1
39 files changed, 772 insertions, 287 deletions
diff --git a/src/tools/rust-analyzer/crates/parser/Cargo.toml b/src/tools/rust-analyzer/crates/parser/Cargo.toml
index d1420de89..6e962abd7 100644
--- a/src/tools/rust-analyzer/crates/parser/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/parser/Cargo.toml
@@ -2,18 +2,23 @@
name = "parser"
version = "0.0.0"
description = "TBD"
-license = "MIT OR Apache-2.0"
-edition = "2021"
-rust-version = "1.65"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
[lib]
doctest = false
[dependencies]
drop_bomb = "0.1.5"
-rustc_lexer = { version = "725.0.0", package = "rustc-ap-rustc_lexer" }
-limit = { path = "../limit", version = "0.0.0" }
+rustc_lexer = { version = "727.0.0", package = "rustc-ap-rustc_lexer" }
+
+limit.workspace = true
[dev-dependencies]
expect-test = "1.4.0"
-sourcegen = { path = "../sourcegen" }
+
+stdx.workspace = true
+sourcegen.workspace = true
diff --git a/src/tools/rust-analyzer/crates/parser/src/event.rs b/src/tools/rust-analyzer/crates/parser/src/event.rs
index b0e70e794..577eb0967 100644
--- a/src/tools/rust-analyzer/crates/parser/src/event.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/event.rs
@@ -74,7 +74,13 @@ pub(crate) enum Event {
kind: SyntaxKind,
n_raw_tokens: u8,
},
-
+ /// When we parse `foo.0.0` or `foo. 0. 0` the lexer will hand us a float literal
+ /// instead of an integer literal followed by a dot as the lexer has no contextual knowledge.
+ /// This event instructs whatever consumes the events to split the float literal into
+ /// the corresponding parts.
+ FloatSplitHack {
+ ends_in_dot: bool,
+ },
Error {
msg: String,
},
@@ -125,6 +131,11 @@ pub(super) fn process(mut events: Vec<Event>) -> Output {
Event::Token { kind, n_raw_tokens } => {
res.token(kind, n_raw_tokens);
}
+ Event::FloatSplitHack { ends_in_dot } => {
+ res.float_split_hack(ends_in_dot);
+ let ev = mem::replace(&mut events[i + 1], Event::tombstone());
+ assert!(matches!(ev, Event::Finish), "{ev:?}");
+ }
Event::Error { msg } => res.error(msg),
}
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
index 485b612f0..15ec9e167 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
@@ -200,6 +200,8 @@ impl BlockLike {
}
}
+const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
+
fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
match p.current() {
T![pub] => {
@@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) {
p.eat(T!['}']);
m.complete(p, ERROR);
}
+
+/// The `parser` passed this is required to at least consume one token if it returns `true`.
+/// If the `parser` returns false, parsing will stop.
+fn delimited(
+ p: &mut Parser<'_>,
+ bra: SyntaxKind,
+ ket: SyntaxKind,
+ delim: SyntaxKind,
+ first_set: TokenSet,
+ mut parser: impl FnMut(&mut Parser<'_>) -> bool,
+) {
+ p.bump(bra);
+ while !p.at(ket) && !p.at(EOF) {
+ if !parser(p) {
+ break;
+ }
+ if !p.at(delim) {
+ if p.at_ts(first_set) {
+ p.error(format!("expected {:?}", delim));
+ } else {
+ break;
+ }
+ } else {
+ p.bump(delim);
+ }
+ }
+ p.expect(ket);
+}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs
index 0cf6a16f8..4ecaa6e6a 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs
@@ -1,5 +1,7 @@
use super::*;
+pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]);
+
pub(super) fn inner_attrs(p: &mut Parser<'_>) {
while p.at(T![#]) && p.nth(1) == T![!] {
attr(p, true);
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
index 8932330b8..4b080102a 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
@@ -1,5 +1,7 @@
mod atom;
+use crate::grammar::attributes::ATTRIBUTE_FIRST;
+
use super::*;
pub(crate) use self::atom::{block_expr, match_arm_list};
@@ -68,6 +70,12 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) {
Err(m) => m,
};
+ if !p.at_ts(EXPR_FIRST) {
+ p.err_and_bump("expected expression, item or let statement");
+ m.abandon(p);
+ return;
+ }
+
if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) {
// test no_semi_after_block
@@ -227,6 +235,12 @@ fn expr_bp(
attributes::outer_attrs(p);
m
});
+
+ if !p.at_ts(EXPR_FIRST) {
+ p.err_recover("expected expression", atom::EXPR_RECOVERY_SET);
+ m.abandon(p);
+ return None;
+ }
let mut lhs = match lhs(p, r) {
Some((lhs, blocklike)) => {
let lhs = lhs.extend_to(p, m);
@@ -379,7 +393,7 @@ fn postfix_expr(
// }
T!['('] if allow_calls => call_expr(p, lhs),
T!['['] if allow_calls => index_expr(p, lhs),
- T![.] => match postfix_dot_expr(p, lhs) {
+ T![.] => match postfix_dot_expr::<false>(p, lhs) {
Ok(it) => it,
Err(it) => {
lhs = it;
@@ -393,35 +407,44 @@ fn postfix_expr(
block_like = BlockLike::NotBlock;
}
return (lhs, block_like);
+}
- fn postfix_dot_expr(
- p: &mut Parser<'_>,
- lhs: CompletedMarker,
- ) -> Result<CompletedMarker, CompletedMarker> {
+fn postfix_dot_expr<const FLOAT_RECOVERY: bool>(
+ p: &mut Parser<'_>,
+ lhs: CompletedMarker,
+) -> Result<CompletedMarker, CompletedMarker> {
+ if !FLOAT_RECOVERY {
assert!(p.at(T![.]));
- if p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) {
- return Ok(method_call_expr(p, lhs));
- }
+ }
+ let nth1 = if FLOAT_RECOVERY { 0 } else { 1 };
+ let nth2 = if FLOAT_RECOVERY { 1 } else { 2 };
- // test await_expr
- // fn foo() {
- // x.await;
- // x.0.await;
- // x.0().await?.hello();
- // }
- if p.nth(1) == T![await] {
- let m = lhs.precede(p);
- p.bump(T![.]);
- p.bump(T![await]);
- return Ok(m.complete(p, AWAIT_EXPR));
- }
+ if p.nth(nth1) == IDENT && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) {
+ return Ok(method_call_expr::<FLOAT_RECOVERY>(p, lhs));
+ }
- if p.at(T![..=]) || p.at(T![..]) {
- return Err(lhs);
+ // test await_expr
+ // fn foo() {
+ // x.await;
+ // x.0.await;
+ // x.0().await?.hello();
+ // x.0.0.await;
+ // x.0. await;
+ // }
+ if p.nth(nth1) == T![await] {
+ let m = lhs.precede(p);
+ if !FLOAT_RECOVERY {
+ p.bump(T![.]);
}
+ p.bump(T![await]);
+ return Ok(m.complete(p, AWAIT_EXPR));
+ }
- Ok(field_expr(p, lhs))
+ if p.at(T![..=]) || p.at(T![..]) {
+ return Err(lhs);
}
+
+ field_expr::<FLOAT_RECOVERY>(p, lhs)
}
// test call_expr
@@ -455,11 +478,22 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
// fn foo() {
// x.foo();
// y.bar::<T>(1, 2,);
+// x.0.0.call();
+// x.0. call();
// }
-fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
- assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])));
+fn method_call_expr<const FLOAT_RECOVERY: bool>(
+ p: &mut Parser<'_>,
+ lhs: CompletedMarker,
+) -> CompletedMarker {
+ if FLOAT_RECOVERY {
+ assert!(p.nth(0) == IDENT && (p.nth(1) == T!['('] || p.nth_at(1, T![::])));
+ } else {
+ assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])));
+ }
let m = lhs.precede(p);
- p.bump_any();
+ if !FLOAT_RECOVERY {
+ p.bump(T![.]);
+ }
name_ref(p);
generic_args::opt_generic_arg_list(p, true);
if p.at(T!['(']) {
@@ -472,21 +506,35 @@ fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker
// fn foo() {
// x.foo;
// x.0.bar;
+// x.0.1;
+// x.0. bar;
// x.0();
// }
-fn field_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
- assert!(p.at(T![.]));
+fn field_expr<const FLOAT_RECOVERY: bool>(
+ p: &mut Parser<'_>,
+ lhs: CompletedMarker,
+) -> Result<CompletedMarker, CompletedMarker> {
+ if !FLOAT_RECOVERY {
+ assert!(p.at(T![.]));
+ }
let m = lhs.precede(p);
- p.bump(T![.]);
+ if !FLOAT_RECOVERY {
+ p.bump(T![.]);
+ }
if p.at(IDENT) || p.at(INT_NUMBER) {
name_ref_or_index(p);
} else if p.at(FLOAT_NUMBER) {
- // FIXME: How to recover and instead parse INT + T![.]?
- p.bump_any();
+ return match p.split_float(m) {
+ (true, m) => {
+ let lhs = m.complete(p, FIELD_EXPR);
+ postfix_dot_expr::<true>(p, lhs)
+ }
+ (false, m) => Ok(m.complete(p, FIELD_EXPR)),
+ };
} else {
p.error("expected field name or number");
}
- m.complete(p, FIELD_EXPR)
+ Ok(m.complete(p, FIELD_EXPR))
}
// test try_expr
@@ -517,23 +565,20 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
m.complete(p, CAST_EXPR)
}
+// test_err arg_list_recovery
+// fn main() {
+// foo(bar::);
+// foo(bar:);
+// foo(bar+);
+// }
fn arg_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
let m = p.start();
- p.bump(T!['(']);
- while !p.at(T![')']) && !p.at(EOF) {
- // test arg_with_attr
- // fn main() {
- // foo(#[attr] 92)
- // }
- if !expr(p) {
- break;
- }
- if !p.at(T![')']) && !p.expect(T![,]) {
- break;
- }
- }
- p.eat(T![')']);
+ // test arg_with_attr
+ // fn main() {
+ // foo(#[attr] 92)
+ // }
+ delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr);
m.complete(p, ARG_LIST);
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
index efa399735..efc260383 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
@@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
T!['{'],
T!['['],
T![|],
- T![move],
+ T![async],
T![box],
+ T![break],
+ T![const],
+ T![continue],
+ T![do],
+ T![for],
T![if],
- T![while],
+ T![let],
+ T![loop],
T![match],
- T![unsafe],
+ T![move],
T![return],
- T![yield],
- T![do],
- T![break],
- T![continue],
- T![async],
+ T![static],
T![try],
- T![const],
- T![loop],
- T![for],
+ T![unsafe],
+ T![while],
+ T![yield],
LIFETIME_IDENT,
]));
-const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]);
+pub(super) const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![')'], T![']']]);
pub(super) fn atom_expr(
p: &mut Parser<'_>,
@@ -116,7 +118,7 @@ pub(super) fn atom_expr(
// fn main() {
// 'loop: impl
// }
- p.error("expected a loop");
+ p.error("expected a loop or block");
m.complete(p, ERROR);
return None;
}
@@ -152,12 +154,12 @@ pub(super) fn atom_expr(
m.complete(p, BLOCK_EXPR)
}
- T![static] | T![async] | T![move] | T![|] => closure_expr(p),
+ T![const] | T![static] | T![async] | T![move] | T![|] => closure_expr(p),
T![for] if la == T![<] => closure_expr(p),
T![for] => for_expr(p, None),
_ => {
- p.err_recover("expected expression", EXPR_RECOVERY_SET);
+ p.err_and_bump("expected expression");
return None;
}
};
@@ -255,7 +257,7 @@ fn array_expr(p: &mut Parser<'_>) -> CompletedMarker {
// }
fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker {
assert!(match p.current() {
- T![static] | T![async] | T![move] | T![|] => true,
+ T![const] | T![static] | T![async] | T![move] | T![|] => true,
T![for] => p.nth(1) == T![<],
_ => false,
});
@@ -265,7 +267,9 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker {
if p.at(T![for]) {
types::for_binder(p);
}
-
+ // test const_closure
+ // fn main() { let cl = const || _ = 0; }
+ p.eat(T![const]);
p.eat(T![static]);
p.eat(T![async]);
p.eat(T![move]);
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
index c438943a0..919d9b91e 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
@@ -5,27 +5,35 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo
if p.at(T![::]) && p.nth(2) == T![<] {
m = p.start();
p.bump(T![::]);
- p.bump(T![<]);
} else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
m = p.start();
- p.bump(T![<]);
} else {
return;
}
- while !p.at(EOF) && !p.at(T![>]) {
- generic_arg(p);
- if !p.at(T![>]) && !p.expect(T![,]) {
- break;
- }
- }
- p.expect(T![>]);
+ delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
m.complete(p, GENERIC_ARG_LIST);
}
+const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[
+ LIFETIME_IDENT,
+ IDENT,
+ T!['{'],
+ T![true],
+ T![false],
+ T![-],
+ INT_NUMBER,
+ FLOAT_NUMBER,
+ CHAR,
+ BYTE,
+ STRING,
+ BYTE_STRING,
+])
+.union(types::TYPE_FIRST);
+
// test generic_arg
// type T = S<i32>;
-fn generic_arg(p: &mut Parser<'_>) {
+fn generic_arg(p: &mut Parser<'_>) -> bool {
match p.current() {
LIFETIME_IDENT => lifetime_arg(p),
T!['{'] | T![true] | T![false] | T![-] => const_arg(p),
@@ -68,8 +76,10 @@ fn generic_arg(p: &mut Parser<'_>) {
}
}
}
- _ => type_arg(p),
+ _ if p.at_ts(types::TYPE_FIRST) => type_arg(p),
+ _ => return false,
}
+ true
}
// test lifetime_arg
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 6db28ef13..7fcf938ba 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
@@ -1,3 +1,5 @@
+use crate::grammar::attributes::ATTRIBUTE_FIRST;
+
use super::*;
pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
@@ -11,32 +13,31 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
fn generic_param_list(p: &mut Parser<'_>) {
assert!(p.at(T![<]));
let m = p.start();
- p.bump(T![<]);
+ delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| {
+ // test generic_param_attribute
+ // fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
+ let m = p.start();
+ attributes::outer_attrs(p);
+ generic_param(p, m)
+ });
- while !p.at(EOF) && !p.at(T![>]) {
- generic_param(p);
- if !p.at(T![>]) && !p.expect(T![,]) {
- break;
- }
- }
- p.expect(T![>]);
m.complete(p, GENERIC_PARAM_LIST);
}
-fn generic_param(p: &mut Parser<'_>) {
- let m = p.start();
- // test generic_param_attribute
- // fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
- attributes::outer_attrs(p);
+const GENERIC_PARAM_FIRST: TokenSet = TokenSet::new(&[IDENT, LIFETIME_IDENT, T![const]]);
+
+fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool {
match p.current() {
LIFETIME_IDENT => lifetime_param(p, m),
IDENT => type_param(p, m),
T![const] => const_param(p, m),
_ => {
m.abandon(p);
- p.err_and_bump("expected type parameter");
+ p.err_and_bump("expected generic parameter");
+ return false;
}
}
+ true
}
// test lifetime_param
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs
index e7d30516b..17f41b8e1 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs
@@ -1,3 +1,5 @@
+use crate::grammar::attributes::ATTRIBUTE_FIRST;
+
use super::*;
// test struct_item
@@ -141,28 +143,31 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
}
}
+const TUPLE_FIELD_FIRST: TokenSet =
+ types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST);
+
fn tuple_field_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
let m = p.start();
- p.bump(T!['(']);
- while !p.at(T![')']) && !p.at(EOF) {
+ delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| {
let m = p.start();
// test tuple_field_attrs
// struct S (#[attr] f32);
attributes::outer_attrs(p);
- opt_visibility(p, true);
+ let has_vis = opt_visibility(p, true);
if !p.at_ts(types::TYPE_FIRST) {
p.error("expected a type");
- m.complete(p, ERROR);
- break;
+ if has_vis {
+ m.complete(p, ERROR);
+ } else {
+ m.abandon(p);
+ }
+ return false;
}
types::type_(p);
m.complete(p, TUPLE_FIELD);
+ true
+ });
- if !p.at(T![')']) {
- p.expect(T![,]);
- }
- }
- p.expect(T![')']);
m.complete(p, TUPLE_FIELD_LIST);
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs
index 20e8e95f0..74eae9151 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs
@@ -1,3 +1,5 @@
+use crate::grammar::attributes::ATTRIBUTE_FIRST;
+
use super::*;
// test param_list
@@ -66,14 +68,20 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) {
}
};
- if !p.at_ts(PARAM_FIRST) {
+ if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
p.error("expected value parameter");
m.abandon(p);
break;
}
param(p, m, flavor);
- if !p.at(ket) {
- p.expect(T![,]);
+ if !p.at(T![,]) {
+ if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
+ p.error("expected `,`");
+ } else {
+ break;
+ }
+ } else {
+ p.bump(T![,]);
}
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs
index af3b6f63c..1064ae997 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs
@@ -67,6 +67,10 @@ fn path_for_qualifier(
}
}
+const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet =
+ items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]]));
+const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET;
+
fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
let m = p.start();
// test qual_paths
@@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
m.complete(p, NAME_REF);
}
_ => {
- p.err_recover("expected identifier", items::ITEM_RECOVERY_SET);
+ let recover_set = match mode {
+ Mode::Use => items::ITEM_RECOVERY_SET,
+ Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET,
+ Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET,
+ };
+ p.err_recover("expected identifier", recover_set);
if empty {
// test_err empty_segment
// use crate::;
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
index 5c6e18fee..7d0b156c5 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
@@ -17,8 +17,9 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[
T![Self],
]));
-const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
+pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
T![')'],
+ T![>],
T![,],
// test_err struct_field_recover
// struct S { f pub g: () }
diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
index b48921f19..100deff46 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
@@ -82,6 +82,7 @@ impl<'a> LexedStr<'a> {
pub fn text(&self, i: usize) -> &str {
self.range_text(i..i + 1)
}
+
pub fn range_text(&self, r: ops::Range<usize>) -> &str {
assert!(r.start < r.end && r.end <= self.len());
let lo = self.start[r.start] as usize;
@@ -216,6 +217,10 @@ impl<'a> Converter<'a> {
rustc_lexer::TokenKind::Caret => T![^],
rustc_lexer::TokenKind::Percent => T![%],
rustc_lexer::TokenKind::Unknown => ERROR,
+ rustc_lexer::TokenKind::UnknownPrefix => {
+ err = "unknown literal prefix";
+ IDENT
+ }
}
};
diff --git a/src/tools/rust-analyzer/crates/parser/src/lib.rs b/src/tools/rust-analyzer/crates/parser/src/lib.rs
index 87be47927..8c5aed023 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lib.rs
@@ -102,10 +102,14 @@ impl TopEntryPoint {
match step {
Step::Enter { .. } => depth += 1,
Step::Exit => depth -= 1,
+ Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+ depth -= 1 + !has_pseudo_dot as usize
+ }
Step::Token { .. } | Step::Error { .. } => (),
}
}
assert!(!first, "no tree at all");
+ assert_eq!(depth, 0, "unbalanced tree");
}
res
diff --git a/src/tools/rust-analyzer/crates/parser/src/output.rs b/src/tools/rust-analyzer/crates/parser/src/output.rs
index 6ca841cfe..41d4c68b2 100644
--- a/src/tools/rust-analyzer/crates/parser/src/output.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/output.rs
@@ -25,53 +25,88 @@ pub struct Output {
#[derive(Debug)]
pub enum Step<'a> {
Token { kind: SyntaxKind, n_input_tokens: u8 },
+ FloatSplit { ends_in_dot: bool },
Enter { kind: SyntaxKind },
Exit,
Error { msg: &'a str },
}
impl Output {
+ const EVENT_MASK: u32 = 0b1;
+ const TAG_MASK: u32 = 0x0000_00F0;
+ const N_INPUT_TOKEN_MASK: u32 = 0x0000_FF00;
+ const KIND_MASK: u32 = 0xFFFF_0000;
+
+ const ERROR_SHIFT: u32 = Self::EVENT_MASK.trailing_ones();
+ const TAG_SHIFT: u32 = Self::TAG_MASK.trailing_zeros();
+ const N_INPUT_TOKEN_SHIFT: u32 = Self::N_INPUT_TOKEN_MASK.trailing_zeros();
+ const KIND_SHIFT: u32 = Self::KIND_MASK.trailing_zeros();
+
+ const TOKEN_EVENT: u8 = 0;
+ const ENTER_EVENT: u8 = 1;
+ const EXIT_EVENT: u8 = 2;
+ const SPLIT_EVENT: u8 = 3;
+
pub fn iter(&self) -> impl Iterator<Item = Step<'_>> {
self.event.iter().map(|&event| {
- if event & 0b1 == 0 {
- return Step::Error { msg: self.error[(event as usize) >> 1].as_str() };
+ if event & Self::EVENT_MASK == 0 {
+ return Step::Error {
+ msg: self.error[(event as usize) >> Self::ERROR_SHIFT].as_str(),
+ };
}
- let tag = ((event & 0x0000_00F0) >> 4) as u8;
+ let tag = ((event & Self::TAG_MASK) >> Self::TAG_SHIFT) as u8;
match tag {
- 0 => {
- let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into();
- let n_input_tokens = ((event & 0x0000_FF00) >> 8) as u8;
+ Self::TOKEN_EVENT => {
+ let kind: SyntaxKind =
+ (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into();
+ let n_input_tokens =
+ ((event & Self::N_INPUT_TOKEN_MASK) >> Self::N_INPUT_TOKEN_SHIFT) as u8;
Step::Token { kind, n_input_tokens }
}
- 1 => {
- let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into();
+ Self::ENTER_EVENT => {
+ let kind: SyntaxKind =
+ (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into();
Step::Enter { kind }
}
- 2 => Step::Exit,
+ Self::EXIT_EVENT => Step::Exit,
+ Self::SPLIT_EVENT => {
+ Step::FloatSplit { ends_in_dot: event & Self::N_INPUT_TOKEN_MASK != 0 }
+ }
_ => unreachable!(),
}
})
}
pub(crate) fn token(&mut self, kind: SyntaxKind, n_tokens: u8) {
- let e = ((kind as u16 as u32) << 16) | ((n_tokens as u32) << 8) | 1;
+ let e = ((kind as u16 as u32) << Self::KIND_SHIFT)
+ | ((n_tokens as u32) << Self::N_INPUT_TOKEN_SHIFT)
+ | Self::EVENT_MASK;
self.event.push(e)
}
+ pub(crate) fn float_split_hack(&mut self, ends_in_dot: bool) {
+ let e = (Self::SPLIT_EVENT as u32) << Self::TAG_SHIFT
+ | ((ends_in_dot as u32) << Self::N_INPUT_TOKEN_SHIFT)
+ | Self::EVENT_MASK;
+ self.event.push(e);
+ }
+
pub(crate) fn enter_node(&mut self, kind: SyntaxKind) {
- let e = ((kind as u16 as u32) << 16) | (1 << 4) | 1;
+ let e = ((kind as u16 as u32) << Self::KIND_SHIFT)
+ | ((Self::ENTER_EVENT as u32) << Self::TAG_SHIFT)
+ | Self::EVENT_MASK;
self.event.push(e)
}
pub(crate) fn leave_node(&mut self) {
- let e = 2 << 4 | 1;
+ let e = (Self::EXIT_EVENT as u32) << Self::TAG_SHIFT | Self::EVENT_MASK;
self.event.push(e)
}
pub(crate) fn error(&mut self, error: String) {
let idx = self.error.len();
self.error.push(error);
- let e = (idx as u32) << 1;
+ let e = (idx as u32) << Self::ERROR_SHIFT;
self.event.push(e);
}
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs
index 48aecb35b..280416ae7 100644
--- a/src/tools/rust-analyzer/crates/parser/src/parser.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs
@@ -181,6 +181,35 @@ impl<'t> Parser<'t> {
self.do_bump(kind, 1);
}
+ /// Advances the parser by one token
+ pub(crate) fn split_float(&mut self, mut marker: Marker) -> (bool, Marker) {
+ assert!(self.at(SyntaxKind::FLOAT_NUMBER));
+ // we have parse `<something>.`
+ // `<something>`.0.1
+ // here we need to insert an extra event
+ //
+ // `<something>`. 0. 1;
+ // here we need to change the follow up parse, the return value will cause us to emulate a dot
+ // the actual splitting happens later
+ let ends_in_dot = !self.inp.is_joint(self.pos);
+ if !ends_in_dot {
+ let new_marker = self.start();
+ let idx = marker.pos as usize;
+ match &mut self.events[idx] {
+ Event::Start { forward_parent, kind } => {
+ *kind = SyntaxKind::FIELD_EXPR;
+ *forward_parent = Some(new_marker.pos - marker.pos);
+ }
+ _ => unreachable!(),
+ }
+ marker.bomb.defuse();
+ marker = new_marker;
+ };
+ self.pos += 1 as usize;
+ self.push_event(Event::FloatSplitHack { ends_in_dot });
+ (ends_in_dot, marker)
+ }
+
/// Advances the parser by one token, remapping its kind.
/// This is useful to create contextual keywords from
/// identifiers. For example, the lexer creates a `union`
diff --git a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
index 2be4050d1..47e4adcbb 100644
--- a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
@@ -43,7 +43,16 @@ impl<'a> LexedStr<'a> {
res.was_joint();
}
res.push(kind);
+ // Tag the token as joint if it is float with a fractional part
+ // we use this jointness to inform the parser about what token split
+ // event to emit when we encounter a float literal in a field access
+ if kind == SyntaxKind::FLOAT_NUMBER {
+ if !self.text(i).ends_with('.') {
+ res.was_joint();
+ }
+ }
}
+
was_joint = true;
}
}
@@ -63,6 +72,9 @@ impl<'a> LexedStr<'a> {
Step::Token { kind, n_input_tokens: n_raw_tokens } => {
builder.token(kind, n_raw_tokens)
}
+ Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+ builder.float_split(has_pseudo_dot)
+ }
Step::Enter { kind } => builder.enter(kind),
Step::Exit => builder.exit(),
Step::Error { msg } => {
@@ -109,6 +121,16 @@ impl Builder<'_, '_> {
self.do_token(kind, n_tokens as usize);
}
+ fn float_split(&mut self, has_pseudo_dot: bool) {
+ match mem::replace(&mut self.state, State::Normal) {
+ State::PendingEnter => unreachable!(),
+ State::PendingExit => (self.sink)(StrStep::Exit),
+ State::Normal => (),
+ }
+ self.eat_trivias();
+ self.do_float_split(has_pseudo_dot);
+ }
+
fn enter(&mut self, kind: SyntaxKind) {
match mem::replace(&mut self.state, State::Normal) {
State::PendingEnter => {
@@ -164,6 +186,37 @@ impl Builder<'_, '_> {
self.pos += n_tokens;
(self.sink)(StrStep::Token { kind, text });
}
+
+ fn do_float_split(&mut self, has_pseudo_dot: bool) {
+ let text = &self.lexed.range_text(self.pos..self.pos + 1);
+ self.pos += 1;
+ match text.split_once('.') {
+ Some((left, right)) => {
+ assert!(!left.is_empty());
+ (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF });
+ (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: left });
+ (self.sink)(StrStep::Exit);
+
+ // here we move the exit up, the original exit has been deleted in process
+ (self.sink)(StrStep::Exit);
+
+ (self.sink)(StrStep::Token { kind: SyntaxKind::DOT, text: "." });
+
+ if has_pseudo_dot {
+ assert!(right.is_empty(), "{left}.{right}");
+ self.state = State::Normal;
+ } else {
+ (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF });
+ (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: right });
+ (self.sink)(StrStep::Exit);
+
+ // the parser creates an unbalanced start node, we are required to close it here
+ self.state = State::PendingExit;
+ }
+ }
+ None => unreachable!(),
+ }
+ }
}
fn n_attached_trivias<'a>(
diff --git a/src/tools/rust-analyzer/crates/parser/src/tests.rs b/src/tools/rust-analyzer/crates/parser/src/tests.rs
index c1b4e9a7d..2fec765bd 100644
--- a/src/tools/rust-analyzer/crates/parser/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/tests.rs
@@ -15,6 +15,7 @@ use crate::{LexedStr, TopEntryPoint};
#[test]
fn lex_ok() {
for case in TestCase::list("lexer/ok") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let actual = lex(&case.text);
expect_file![case.rast].assert_eq(&actual)
}
@@ -23,6 +24,7 @@ fn lex_ok() {
#[test]
fn lex_err() {
for case in TestCase::list("lexer/err") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let actual = lex(&case.text);
expect_file![case.rast].assert_eq(&actual)
}
@@ -46,6 +48,7 @@ fn lex(text: &str) -> String {
#[test]
fn parse_ok() {
for case in TestCase::list("parser/ok") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual);
@@ -55,6 +58,7 @@ fn parse_ok() {
#[test]
fn parse_inline_ok() {
for case in TestCase::list("parser/inline/ok") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual);
@@ -64,6 +68,7 @@ fn parse_inline_ok() {
#[test]
fn parse_err() {
for case in TestCase::list("parser/err") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual)
@@ -73,6 +78,7 @@ fn parse_err() {
#[test]
fn parse_inline_err() {
for case in TestCase::list("parser/inline/err") {
+ let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual)
diff --git a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
index e626b4f27..40f92e588 100644
--- a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
@@ -51,6 +51,9 @@ fn expr() {
check(PrefixEntryPoint::Expr, "-1", "-1");
check(PrefixEntryPoint::Expr, "fn foo() {}", "fn");
check(PrefixEntryPoint::Expr, "#[attr] ()", "#[attr] ()");
+ check(PrefixEntryPoint::Expr, "foo.0", "foo.0");
+ check(PrefixEntryPoint::Expr, "foo.0.1", "foo.0.1");
+ check(PrefixEntryPoint::Expr, "foo.0. foo", "foo.0. foo");
}
#[test]
@@ -88,6 +91,7 @@ fn check(entry: PrefixEntryPoint, input: &str, prefix: &str) {
for step in entry.parse(&input).iter() {
match step {
Step::Token { n_input_tokens, .. } => n_tokens += n_input_tokens as usize,
+ Step::FloatSplit { .. } => n_tokens += 1,
Step::Enter { .. } | Step::Exit | Step::Error { .. } => (),
}
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs b/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs
index eb640dc7f..49dd9e293 100644
--- a/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs
@@ -65,7 +65,7 @@ fn macro_stmt() {
MACRO_STMTS
ERROR
SHEBANG "#!/usr/bin/rust"
- error 0: expected expression
+ error 0: expected expression, item or let statement
"##]],
);
check(
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
index a01543217..cdc01863a 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
@@ -44,8 +44,7 @@ SOURCE_FILE
IDENT "T"
SEMICOLON ";"
WHITESPACE "\n"
-error 9: expected type parameter
-error 11: expected COMMA
+error 9: expected generic parameter
error 11: expected R_ANGLE
error 11: expected `;`, `{`, or `(`
error 12: expected an item
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast
index eec84a0c6..b485c71ab 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast
@@ -43,17 +43,14 @@ SOURCE_FILE
IDENT "Box"
GENERIC_ARG_LIST
L_ANGLE "<"
- TYPE_ARG
- ERROR
- AT "@"
- WHITESPACE " "
- TUPLE_FIELD
- PATH_TYPE
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Any"
- ERROR
+ ERROR
+ AT "@"
+ WHITESPACE " "
+ MACRO_CALL
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Any"
ERROR
R_ANGLE ">"
ERROR
@@ -69,17 +66,14 @@ SOURCE_FILE
ERROR
SEMICOLON ";"
WHITESPACE "\n\n"
-error 67: expected type
-error 68: expected COMMA
-error 68: expected R_ANGLE
-error 68: expected COMMA
-error 68: expected R_ANGLE
-error 68: expected COMMA
-error 68: expected R_ANGLE
-error 68: expected COMMA
-error 72: expected COMMA
-error 72: expected a type
-error 72: expected R_PAREN
+error 67: expected R_ANGLE
+error 67: expected R_ANGLE
+error 67: expected R_ANGLE
+error 67: expected R_PAREN
+error 67: expected SEMICOLON
+error 67: expected an item
+error 72: expected BANG
+error 72: expected `{`, `[`, `(`
error 72: expected SEMICOLON
error 72: expected an item
error 73: expected an item
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast
index 900394bd9..d97fc6c72 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast
@@ -145,27 +145,29 @@ SOURCE_FILE
error 16: expected expression
error 17: expected R_BRACK
error 17: expected SEMICOLON
-error 17: expected expression
+error 17: expected expression, item or let statement
error 25: expected a name
error 26: expected `;`, `{`, or `(`
error 30: expected pattern
error 31: expected SEMICOLON
error 53: expected expression
+error 54: expected R_PAREN
error 54: expected SEMICOLON
-error 54: expected expression
+error 54: expected expression, item or let statement
error 60: expected type
error 60: expected `{`
-error 60: expected expression
+error 60: expected expression, item or let statement
error 65: expected pattern
error 65: expected SEMICOLON
-error 65: expected expression
+error 65: expected expression, item or let statement
error 92: expected expression
+error 93: expected R_PAREN
error 93: expected SEMICOLON
-error 93: expected expression
-error 95: expected expression
-error 96: expected expression
+error 93: expected expression, item or let statement
+error 95: expected expression, item or let statement
+error 96: expected expression, item or let statement
error 103: expected a name
error 104: expected `{`
error 108: expected pattern
error 108: expected SEMICOLON
-error 108: expected expression
+error 108: expected expression, item or let statement
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
index d374f8661..f0dbc9b10 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast
@@ -168,75 +168,21 @@ SOURCE_FILE
L_PAREN "("
ERROR
QUESTION "?"
- EXPR_STMT
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Sized"
+ TYPE_ARG
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Sized"
ERROR
R_PAREN ")"
WHITESPACE " "
ERROR
PLUS "+"
WHITESPACE " "
- TUPLE_EXPR
- L_PAREN "("
- CLOSURE_EXPR
- FOR_KW "for"
- GENERIC_PARAM_LIST
- L_ANGLE "<"
- LIFETIME_PARAM
- LIFETIME
- LIFETIME_IDENT "'a"
- R_ANGLE ">"
- WHITESPACE " "
- BIN_EXPR
- BIN_EXPR
- BIN_EXPR
- BIN_EXPR
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Trait"
- L_ANGLE "<"
- ERROR
- LIFETIME_IDENT "'a"
- R_ANGLE ">"
- ERROR
- R_PAREN ")"
- WHITESPACE " "
- PLUS "+"
- WHITESPACE " "
- PAREN_EXPR
- L_PAREN "("
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Copy"
- R_PAREN ")"
- R_ANGLE ">"
- ERROR
- SEMICOLON ";"
- WHITESPACE "\n "
- LET_EXPR
- LET_KW "let"
- WHITESPACE " "
- WILDCARD_PAT
- UNDERSCORE "_"
- ERROR
- COLON ":"
- WHITESPACE " "
+ EXPR_STMT
BIN_EXPR
BIN_EXPR
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Box"
- L_ANGLE "<"
TUPLE_EXPR
L_PAREN "("
CLOSURE_EXPR
@@ -250,78 +196,117 @@ SOURCE_FILE
WHITESPACE " "
BIN_EXPR
BIN_EXPR
- BIN_EXPR
- BIN_EXPR
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Trait"
- L_ANGLE "<"
- ERROR
- LIFETIME_IDENT "'a"
- R_ANGLE ">"
- ERROR
- R_PAREN ")"
- WHITESPACE " "
- PLUS "+"
- WHITESPACE " "
- PAREN_EXPR
- L_PAREN "("
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "Copy"
- R_PAREN ")"
- WHITESPACE " "
- PLUS "+"
- WHITESPACE " "
- PAREN_EXPR
- L_PAREN "("
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Trait"
+ L_ANGLE "<"
ERROR
- QUESTION "?"
+ LIFETIME_IDENT "'a"
+ R_ANGLE ">"
+ R_PAREN ")"
+ WHITESPACE " "
+ PLUS "+"
+ WHITESPACE " "
+ PAREN_EXPR
+ L_PAREN "("
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
- IDENT "Sized"
+ IDENT "Copy"
R_PAREN ")"
R_ANGLE ">"
ERROR
SEMICOLON ";"
+ WHITESPACE "\n "
+ LET_STMT
+ LET_KW "let"
+ WHITESPACE " "
+ WILDCARD_PAT
+ UNDERSCORE "_"
+ COLON ":"
+ WHITESPACE " "
+ DYN_TRAIT_TYPE
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Box"
+ GENERIC_ARG_LIST
+ L_ANGLE "<"
+ TYPE_ARG
+ PAREN_TYPE
+ L_PAREN "("
+ FOR_TYPE
+ FOR_KW "for"
+ GENERIC_PARAM_LIST
+ L_ANGLE "<"
+ LIFETIME_PARAM
+ LIFETIME
+ LIFETIME_IDENT "'a"
+ R_ANGLE ">"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Trait"
+ GENERIC_ARG_LIST
+ L_ANGLE "<"
+ LIFETIME_ARG
+ LIFETIME
+ LIFETIME_IDENT "'a"
+ R_ANGLE ">"
+ R_PAREN ")"
+ WHITESPACE " "
+ PLUS "+"
+ WHITESPACE " "
+ TYPE_BOUND
+ L_PAREN "("
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Copy"
+ R_PAREN ")"
+ WHITESPACE " "
+ PLUS "+"
+ WHITESPACE " "
+ TYPE_BOUND
+ L_PAREN "("
+ QUESTION "?"
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Sized"
+ R_PAREN ")"
+ ERROR
+ R_ANGLE ">"
+ SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
-error 88: expected COMMA
error 88: expected R_ANGLE
error 121: expected SEMICOLON
-error 121: expected expression
+error 121: expected expression, item or let statement
error 140: expected type
error 141: expected R_PAREN
error 141: expected COMMA
-error 141: expected R_ANGLE
-error 141: expected SEMICOLON
+error 146: expected R_ANGLE
error 146: expected SEMICOLON
-error 146: expected expression
-error 148: expected expression
+error 146: expected expression, item or let statement
+error 148: expected expression, item or let statement
error 158: expected `|`
error 158: expected COMMA
error 165: expected expression
error 168: expected expression
error 179: expected expression
-error 180: expected COMMA
-error 190: expected EQ
-error 190: expected expression
-error 191: expected COMMA
-error 204: expected `|`
-error 204: expected COMMA
-error 211: expected expression
-error 214: expected expression
-error 228: expected expression
-error 229: expected R_PAREN
-error 229: expected COMMA
-error 236: expected expression
-error 237: expected COMMA
-error 237: expected expression
-error 237: expected R_PAREN
+error 180: expected SEMICOLON
+error 215: expected R_ANGLE
+error 235: expected SEMICOLON
+error 235: expected expression, item or let statement
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast
index 6b49724ec..b6bc00883 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast
@@ -156,8 +156,7 @@ SOURCE_FILE
PATH_SEGMENT
NAME_REF
IDENT "i32"
- WHITESPACE " "
- ERROR
+ WHITESPACE " "
ERROR
L_CURLY "{"
R_CURLY "}"
@@ -199,10 +198,8 @@ error 95: expected type
error 95: expected COMMA
error 96: expected field
error 98: expected field declaration
+error 371: expected R_PAREN
error 371: expected COMMA
-error 372: expected a type
-error 372: expected R_PAREN
-error 372: expected COMMA
error 372: expected enum variant
error 374: expected enum variant
error 494: expected pattern
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast
index 9cea337ce..1cdc6e6e7 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast
@@ -72,4 +72,4 @@ SOURCE_FILE
error 24: expected existential, fn, trait or impl
error 41: expected existential, fn, trait or impl
error 56: expected a block
-error 75: expected a loop
+error 75: expected a loop or block
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast
index 3a05bfee1..207a5c24d 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast
@@ -12,7 +12,7 @@ SOURCE_FILE
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
- EXPR_STMT
+ BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
@@ -41,13 +41,14 @@ SOURCE_FILE
COLON2 "::"
ERROR
L_ANGLE "<"
- BIN_EXPR
- PATH_EXPR
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "nope"
- SHR ">>"
+ TYPE_ARG
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "nope"
+ R_ANGLE ">"
+ R_ANGLE ">"
ERROR
SEMICOLON ";"
WHITESPACE "\n"
@@ -114,8 +115,6 @@ SOURCE_FILE
WHITESPACE "\n"
error 30: expected identifier
error 31: expected COMMA
-error 31: expected R_ANGLE
-error 31: expected SEMICOLON
error 37: expected expression
error 75: expected identifier
error 76: expected SEMICOLON
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
index 56cea4b15..ea5203fb9 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
@@ -23,6 +23,6 @@ SOURCE_FILE
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
-error 22: expected a loop
+error 22: expected a loop or block
error 27: expected type
error 27: expected `{`
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
new file mode 100644
index 000000000..5d0fe859c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
@@ -0,0 +1,77 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "main"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE "\n "
+ EXPR_STMT
+ CALL_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "foo"
+ ARG_LIST
+ L_PAREN "("
+ PATH_EXPR
+ PATH
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "bar"
+ COLON2 "::"
+ R_PAREN ")"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ CALL_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "foo"
+ ARG_LIST
+ L_PAREN "("
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "bar"
+ ERROR
+ COLON ":"
+ R_PAREN ")"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ CALL_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "foo"
+ ARG_LIST
+ L_PAREN "("
+ BIN_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "bar"
+ PLUS "+"
+ R_PAREN ")"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+ R_CURLY "}"
+ WHITESPACE "\n"
+error 25: expected identifier
+error 39: expected COMMA
+error 39: expected expression
+error 55: expected expression
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
new file mode 100644
index 000000000..0e7ac9cc3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
@@ -0,0 +1,5 @@
+fn main() {
+ foo(bar::);
+ foo(bar:);
+ foo(bar+);
+}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
index e72df374d..ea50ad35d 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
@@ -49,5 +49,5 @@ SOURCE_FILE
R_CURLY "}"
WHITESPACE "\n"
error 6: missing type for function parameter
-error 6: expected COMMA
+error 6: expected `,`
error 16: missing type for function parameter
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
index 8498724b9..dd27dc489 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
@@ -41,6 +41,39 @@ SOURCE_FILE
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
+ FIELD_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "1"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ FIELD_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ WHITESPACE " "
+ NAME_REF
+ IDENT "bar"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
CALL_EXPR
FIELD_EXPR
PATH_EXPR
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
index b8da2ddc3..98dbe45a7 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs
@@ -1,5 +1,7 @@
fn foo() {
x.foo;
x.0.bar;
+ x.0.1;
+ x.0. bar;
x.0();
}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
index dcbcfe123..b28b8eb67 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast
@@ -58,6 +58,49 @@ SOURCE_FILE
COMMA ","
R_PAREN ")"
SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ METHOD_CALL_EXPR
+ FIELD_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ NAME_REF
+ IDENT "call"
+ ARG_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ METHOD_CALL_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ WHITESPACE " "
+ NAME_REF
+ IDENT "call"
+ ARG_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
index 1a3aa35ae..48bb6381e 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs
@@ -1,4 +1,6 @@
fn foo() {
x.foo();
y.bar::<T>(1, 2,);
+ x.0.0.call();
+ x.0. call();
}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
index 9d37ada0d..af713a220 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast
@@ -65,6 +65,41 @@ SOURCE_FILE
L_PAREN "("
R_PAREN ")"
SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ AWAIT_EXPR
+ FIELD_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ AWAIT_KW "await"
+ SEMICOLON ";"
+ WHITESPACE "\n "
+ EXPR_STMT
+ AWAIT_EXPR
+ FIELD_EXPR
+ PATH_EXPR
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "x"
+ DOT "."
+ NAME_REF
+ INT_NUMBER "0"
+ DOT "."
+ WHITESPACE " "
+ AWAIT_KW "await"
+ SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
index d2ba89ca6..fe9a3211b 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs
@@ -2,4 +2,6 @@ fn foo() {
x.await;
x.0.await;
x.0().await?.hello();
+ x.0.0.await;
+ x.0. await;
}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast
new file mode 100644
index 000000000..06442a1d0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast
@@ -0,0 +1,42 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "main"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ WHITESPACE " "
+ LET_STMT
+ LET_KW "let"
+ WHITESPACE " "
+ IDENT_PAT
+ NAME
+ IDENT "cl"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ CLOSURE_EXPR
+ CONST_KW "const"
+ WHITESPACE " "
+ PARAM_LIST
+ PIPE "|"
+ PIPE "|"
+ WHITESPACE " "
+ BIN_EXPR
+ UNDERSCORE_EXPR
+ UNDERSCORE "_"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ LITERAL
+ INT_NUMBER "0"
+ SEMICOLON ";"
+ WHITESPACE " "
+ R_CURLY "}"
+ WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs
new file mode 100644
index 000000000..0c05cc70b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs
@@ -0,0 +1 @@
+fn main() { let cl = const || _ = 0; }