about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/crates/parser
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@dend.ro>2024-03-10 08:47:38 +0200
committerLaurențiu Nicola <lnicola@dend.ro>2024-03-10 08:47:38 +0200
commit56493e4cbd6262adae03e73aefb8a9e618a9fc2d (patch)
tree404d4a38aff53e1c880c2708478fdcaf0b2f6e61 /src/tools/rust-analyzer/crates/parser
parent5bc7b9ac8ace5312e1d2cdc2722715cf58d4f926 (diff)
parent574e23ec508064613783cba3d1833a95fd9a5080 (diff)
downloadrust-56493e4cbd6262adae03e73aefb8a9e618a9fc2d.tar.gz
rust-56493e4cbd6262adae03e73aefb8a9e618a9fc2d.zip
Merge commit '574e23ec508064613783cba3d1833a95fd9a5080' into sync-from-ra
Diffstat (limited to 'src/tools/rust-analyzer/crates/parser')
-rw-r--r--src/tools/rust-analyzer/crates/parser/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar.rs6
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs14
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/items.rs45
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs6
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/params.rs7
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs8
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/types.rs6
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lexed_str.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/parser.rs9
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/shortcuts.rs19
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rast88
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rs5
16 files changed, 155 insertions, 69 deletions
diff --git a/src/tools/rust-analyzer/crates/parser/Cargo.toml b/src/tools/rust-analyzer/crates/parser/Cargo.toml
index e74b340126c..1f84e3f3af3 100644
--- a/src/tools/rust-analyzer/crates/parser/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/parser/Cargo.toml
@@ -15,6 +15,7 @@ doctest = false
 drop_bomb = "0.1.5"
 ra-ap-rustc_lexer.workspace = true
 limit.workspace = true
+tracing = { workspace = true, optional = true }
 
 [dev-dependencies]
 expect-test = "1.4.0"
@@ -23,6 +24,7 @@ stdx.workspace = true
 sourcegen.workspace = true
 
 [features]
+default = ["tracing"]
 in-rust-tree = []
 
 [lints]
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
index 34715628f18..4e5837312fe 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
@@ -244,7 +244,7 @@ impl BlockLike {
     }
 }
 
-const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
+const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub]]);
 
 fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
     if !p.at(T![pub]) {
@@ -416,14 +416,12 @@ fn delimited(
         if !parser(p) {
             break;
         }
-        if !p.at(delim) {
+        if !p.eat(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/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
index 6b660180f82..861fcedda2a 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs
@@ -211,9 +211,8 @@ fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
         T![>] if p.at(T![>>])  => (9,  T![>>],  Left),
         T![>] if p.at(T![>=])  => (5,  T![>=],  Left),
         T![>]                  => (5,  T![>],   Left),
-        T![=] if p.at(T![=>])  => NOT_AN_OP,
         T![=] if p.at(T![==])  => (5,  T![==],  Left),
-        T![=]                  => (1,  T![=],   Right),
+        T![=] if !p.at(T![=>]) => (1,  T![=],   Right),
         T![<] if p.at(T![<=])  => (5,  T![<=],  Left),
         T![<] if p.at(T![<<=]) => (1,  T![<<=], Right),
         T![<] if p.at(T![<<])  => (9,  T![<<],  Left),
@@ -247,7 +246,7 @@ fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
 fn expr_bp(
     p: &mut Parser<'_>,
     m: Option<Marker>,
-    mut r: Restrictions,
+    r: Restrictions,
     bp: u8,
 ) -> Option<(CompletedMarker, BlockLike)> {
     let m = m.unwrap_or_else(|| {
@@ -295,10 +294,6 @@ fn expr_bp(
         let m = lhs.precede(p);
         p.bump(op);
 
-        // test binop_resets_statementness
-        // fn f() { v = {1}&2; }
-        r = Restrictions { prefer_stmt: false, ..r };
-
         if is_range {
             // test postfix_range
             // fn foo() {
@@ -319,6 +314,9 @@ fn expr_bp(
             Associativity::Left => op_bp + 1,
             Associativity::Right => op_bp,
         };
+
+        // test binop_resets_statementness
+        // fn f() { v = {1}&2; }
         expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp);
         lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
     }
@@ -345,7 +343,7 @@ fn lhs(p: &mut Parser<'_>, r: Restrictions) -> Option<(CompletedMarker, BlockLik
         T![&] => {
             m = p.start();
             p.bump(T![&]);
-            if p.at_contextual_kw(T![raw]) && (p.nth_at(1, T![mut]) || p.nth_at(1, T![const])) {
+            if p.at_contextual_kw(T![raw]) && [T![mut], T![const]].contains(&p.nth(1)) {
                 p.bump_remap(T![raw]);
                 p.bump_any();
             } else {
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 48600641ad0..72848a1f2b7 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
@@ -147,7 +147,7 @@ pub(super) fn atom_expr(
         T![async] if la == T![move] && p.nth(2) == T!['{'] => {
             let m = p.start();
             p.bump(T![async]);
-            p.eat(T![move]);
+            p.bump(T![move]);
             stmt_list(p);
             m.complete(p, BLOCK_EXPR)
         }
@@ -390,8 +390,7 @@ fn if_expr(p: &mut Parser<'_>) -> CompletedMarker {
     p.bump(T![if]);
     expr_no_struct(p);
     block_expr(p);
-    if p.at(T![else]) {
-        p.bump(T![else]);
+    if p.eat(T![else]) {
         if p.at(T![if]) {
             if_expr(p);
         } else {
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 4498daf21a3..6c05abc0238 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
@@ -170,7 +170,7 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
                 _ => (),
             }
             if paths::is_use_path_start(p) {
-                types::path_type_(p, false);
+                types::path_type_bounds(p, false);
             } else {
                 m.abandon(p);
                 return false;
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
index 243a219525a..25c00ccf5f3 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
@@ -70,8 +70,7 @@ pub(super) fn item_or_macro(p: &mut Parser<'_>, stop_on_r_curly: bool) {
     // macro_rules! {};
     // macro_rules! ()
     // macro_rules! []
-    let no_ident = p.at_contextual_kw(T![macro_rules]) && p.nth_at(1, BANG) && !p.nth_at(2, IDENT);
-    if paths::is_use_path_start(p) || no_ident {
+    if paths::is_use_path_start(p) {
         macro_call(p, m);
         return;
     }
@@ -156,27 +155,19 @@ pub(super) fn opt_item(p: &mut Parser<'_>, m: Marker) -> Result<(), Marker> {
             // impl T for Foo {
             //     default async fn foo() {}
             // }
-            T![async] => {
-                let mut maybe_fn = p.nth(2);
-                let is_unsafe = if matches!(maybe_fn, T![unsafe]) {
-                    // test default_async_unsafe_fn
-                    // impl T for Foo {
-                    //     default async unsafe fn foo() {}
-                    // }
-                    maybe_fn = p.nth(3);
-                    true
-                } else {
-                    false
-                };
-
-                if matches!(maybe_fn, T![fn]) {
-                    p.bump_remap(T![default]);
-                    p.bump(T![async]);
-                    if is_unsafe {
-                        p.bump(T![unsafe]);
-                    }
-                    has_mods = true;
-                }
+            T![async]
+                if p.nth_at(2, T![fn]) || (p.nth_at(2, T![unsafe]) && p.nth_at(3, T![fn])) =>
+            {
+                p.bump_remap(T![default]);
+                p.bump(T![async]);
+
+                // test default_async_unsafe_fn
+                // impl T for Foo {
+                //     default async unsafe fn foo() {}
+                // }
+                p.eat(T![unsafe]);
+
+                has_mods = true;
             }
             _ => (),
         }
@@ -419,11 +410,9 @@ fn fn_(p: &mut Parser<'_>, m: Marker) {
     // fn foo<T>() where T: Copy {}
     generic_params::opt_where_clause(p);
 
-    if p.at(T![;]) {
-        // test fn_decl
-        // trait T { fn foo(); }
-        p.bump(T![;]);
-    } else {
+    // test fn_decl
+    // trait T { fn foo(); }
+    if !p.eat(T![;]) {
         expressions::block_expr(p);
     }
     m.complete(p, FN);
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs
index a8a1ccb15e6..c215185d632 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs
@@ -119,11 +119,11 @@ fn not_a_qualified_path(p: &Parser<'_>) -> bool {
     // we disambiguate it in favor of generics (`impl<T> ::absolute::Path<T> { ... }`)
     // because this is what almost always expected in practice, qualified paths in impls
     // (`impl <Type>::AssocTy { ... }`) aren't even allowed by type checker at the moment.
-    if p.nth(1) == T![#] || p.nth(1) == T![>] || p.nth(1) == T![const] {
+    if [T![#], T![>], T![const]].contains(&p.nth(1)) {
         return true;
     }
-    (p.nth(1) == LIFETIME_IDENT || p.nth(1) == IDENT)
-        && (p.nth(2) == T![>] || p.nth(2) == T![,] || p.nth(2) == T![:] || p.nth(2) == T![=])
+    ([LIFETIME_IDENT, IDENT].contains(&p.nth(1)))
+        && ([T![>], T![,], T![:], T![=]].contains(&p.nth(2)))
 }
 
 // test_err impl_type
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 846da28cb01..c535267c165 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs
@@ -76,19 +76,16 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) {
             m.abandon(p);
             if p.eat(T![,]) {
                 continue;
-            } else {
-                break;
             }
+            break;
         }
         param(p, m, flavor);
-        if !p.at(T![,]) {
+        if !p.eat(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/patterns.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs
index 50367423379..eff6b664049 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs
@@ -255,9 +255,7 @@ fn is_literal_pat_start(p: &Parser<'_>) -> bool {
 fn literal_pat(p: &mut Parser<'_>) -> CompletedMarker {
     assert!(is_literal_pat_start(p));
     let m = p.start();
-    if p.at(T![-]) {
-        p.bump(T![-]);
-    }
+    p.eat(T![-]);
     expressions::literal(p);
     m.complete(p, LITERAL_PAT)
 }
@@ -468,14 +466,12 @@ fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker {
 fn pat_list(p: &mut Parser<'_>, ket: SyntaxKind) {
     while !p.at(EOF) && !p.at(ket) {
         pattern_top(p);
-        if !p.at(T![,]) {
+        if !p.eat(T![,]) {
             if p.at_ts(PAT_TOP_FIRST) {
                 p.error(format!("expected {:?}, got {:?}", T![,], p.current()));
             } else {
                 break;
             }
-        } else {
-            p.bump(T![,]);
         }
     }
 }
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 96a6cdeaaff..18ec570cd56 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs
@@ -48,7 +48,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) {
         T![impl] => impl_trait_type(p),
         T![dyn] => dyn_trait_type(p),
         // Some path types are not allowed to have bounds (no plus)
-        T![<] => path_type_(p, allow_bounds),
+        T![<] => path_type_bounds(p, allow_bounds),
         _ if paths::is_path_start(p) => path_or_macro_type_(p, allow_bounds),
         LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p),
         _ => {
@@ -294,7 +294,7 @@ fn bare_dyn_trait_type(p: &mut Parser<'_>) {
 // type C = self::Foo;
 // type D = super::Foo;
 pub(super) fn path_type(p: &mut Parser<'_>) {
-    path_type_(p, true);
+    path_type_bounds(p, true);
 }
 
 // test macro_call_type
@@ -323,7 +323,7 @@ fn path_or_macro_type_(p: &mut Parser<'_>, allow_bounds: bool) {
     }
 }
 
-pub(super) fn path_type_(p: &mut Parser<'_>, allow_bounds: bool) {
+pub(super) fn path_type_bounds(p: &mut Parser<'_>, allow_bounds: bool) {
     assert!(paths::is_path_start(p));
     let m = p.start();
     paths::type_path(p);
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 2da9184693d..48e4c8a6225 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
@@ -31,6 +31,7 @@ struct LexError {
 
 impl<'a> LexedStr<'a> {
     pub fn new(text: &'a str) -> LexedStr<'a> {
+        let _p = tracing::span!(tracing::Level::INFO, "LexedStr::new").entered();
         let mut conv = Converter::new(text);
         if let Some(shebang_len) = rustc_lexer::strip_shebang(text) {
             conv.res.push(SHEBANG, conv.offset);
diff --git a/src/tools/rust-analyzer/crates/parser/src/lib.rs b/src/tools/rust-analyzer/crates/parser/src/lib.rs
index 3ca285e787e..86c771c0008 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lib.rs
@@ -87,6 +87,7 @@ pub enum TopEntryPoint {
 
 impl TopEntryPoint {
     pub fn parse(&self, input: &Input) -> Output {
+        let _p = tracing::span!(tracing::Level::INFO, "TopEntryPoint::parse", ?self).entered();
         let entry_point: fn(&'_ mut parser::Parser<'_>) = match self {
             TopEntryPoint::SourceFile => grammar::entry::top::source_file,
             TopEntryPoint::MacroStmts => grammar::entry::top::macro_stmts,
diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs
index ef413c63754..051461243af 100644
--- a/src/tools/rust-analyzer/crates/parser/src/parser.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs
@@ -250,12 +250,9 @@ impl<'t> Parser<'t> {
 
     /// Create an error node and consume the next token.
     pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) {
-        match self.current() {
-            T!['{'] | T!['}'] => {
-                self.error(message);
-                return;
-            }
-            _ => (),
+        if matches!(self.current(), T!['{'] | T!['}']) {
+            self.error(message);
+            return;
         }
 
         if self.at_ts(recovery) {
diff --git a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
index 57005a6834c..cc2b63d1e66 100644
--- a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
@@ -26,6 +26,7 @@ pub enum StrStep<'a> {
 
 impl LexedStr<'_> {
     pub fn to_input(&self) -> crate::Input {
+        let _p = tracing::span!(tracing::Level::INFO, "LexedStr::to_input").entered();
         let mut res = crate::Input::default();
         let mut was_joint = false;
         for i in 0..self.len() {
@@ -189,7 +190,7 @@ impl Builder<'_, '_> {
 
     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());
@@ -215,8 +216,22 @@ impl Builder<'_, '_> {
                     self.state = State::PendingExit;
                 }
             }
-            None => unreachable!(),
+            None => {
+                // illegal float literal which doesn't have dot in form (like 1e0)
+                // we should emit an error node here
+                (self.sink)(StrStep::Error { msg: "illegal float literal", pos: self.pos });
+                (self.sink)(StrStep::Enter { kind: SyntaxKind::ERROR });
+                (self.sink)(StrStep::Token { kind: SyntaxKind::FLOAT_NUMBER, text });
+                (self.sink)(StrStep::Exit);
+
+                // move up
+                (self.sink)(StrStep::Exit);
+
+                self.state = if has_pseudo_dot { State::Normal } else { State::PendingExit };
+            }
         }
+
+        self.pos += 1;
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rast
new file mode 100644
index 00000000000..d6ad7334839
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rast
@@ -0,0 +1,88 @@
+SOURCE_FILE
+  STRUCT
+    STRUCT_KW "struct"
+    WHITESPACE " "
+    NAME
+      IDENT "S"
+    TUPLE_FIELD_LIST
+      L_PAREN "("
+      TUPLE_FIELD
+        PATH_TYPE
+          PATH
+            PATH_SEGMENT
+              NAME_REF
+                IDENT "i32"
+      COMMA ","
+      WHITESPACE " "
+      TUPLE_FIELD
+        PATH_TYPE
+          PATH
+            PATH_SEGMENT
+              NAME_REF
+                IDENT "i32"
+      R_PAREN ")"
+    SEMICOLON ";"
+  WHITESPACE "\n"
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "f"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        WHITESPACE "\n    "
+        LET_STMT
+          LET_KW "let"
+          WHITESPACE " "
+          IDENT_PAT
+            NAME
+              IDENT "s"
+          WHITESPACE " "
+          EQ "="
+          WHITESPACE " "
+          CALL_EXPR
+            PATH_EXPR
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "S"
+            ARG_LIST
+              L_PAREN "("
+              LITERAL
+                INT_NUMBER "1"
+              COMMA ","
+              WHITESPACE " "
+              LITERAL
+                INT_NUMBER "2"
+              R_PAREN ")"
+          SEMICOLON ";"
+        WHITESPACE "\n    "
+        LET_STMT
+          LET_KW "let"
+          WHITESPACE " "
+          IDENT_PAT
+            NAME
+              IDENT "a"
+          WHITESPACE " "
+          EQ "="
+          WHITESPACE " "
+          FIELD_EXPR
+            FIELD_EXPR
+              PATH_EXPR
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "s"
+              DOT "."
+              ERROR
+                FLOAT_NUMBER "1e0"
+          SEMICOLON ";"
+        WHITESPACE "\n"
+        R_CURLY "}"
+  WHITESPACE "\n"
+error 42: illegal float literal
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rs
new file mode 100644
index 00000000000..648ef5e0430
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0054_float_split_scientific_notation.rs
@@ -0,0 +1,5 @@
+struct S(i32, i32);
+fn f() {
+    let s = S(1, 2);
+    let a = s.1e0;
+}