Repository: rust-lang/rust-analyzer
Pull request #12178 (2022-05-06 16:39:30)

fix: Fix panic when a macro passes a float token to another macro
Fixes https://github.com/rust-lang/rust-analyzer/issues/12170 (num-traits no longer causes a panic)


Patch:
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index 83d22af923..fb6f8d66c6 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -740,6 +740,7 @@ struct TtTreeSink<'a> {
     text_pos: TextSize,
     inner: SyntaxTreeBuilder,
     token_map: TokenMap,
+    remaining_float_lit_text: String,
 }
 
 impl<'a> TtTreeSink<'a> {
@@ -751,6 +752,7 @@ impl<'a> TtTreeSink<'a> {
             text_pos: 0.into(),
             inner: SyntaxTreeBuilder::default(),
             token_map: TokenMap::default(),
+            remaining_float_lit_text: String::new(),
         }
     }
 
@@ -777,6 +779,54 @@ impl<'a> TtTreeSink<'a> {
             n_tokens = 2;
         }
 
+        // We need to split a float `tt::Literal` into up to 3 tokens consumed by the parser.
+        match self.cursor.token_tree() {
+            Some(tt::buffer::TokenTreeRef::Subtree(sub, _)) if sub.delimiter.is_none() => {
+                self.cursor = self.cursor.subtree().unwrap()
+            }
+            _ => {}
+        }
+        let literal = match self.cursor.token_tree() {
+            Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Literal(lit), _)) => Some(lit),
+            _ => None,
+        };
+        if matches!(
+            kind,
+            FLOAT_NUMBER_PART | FLOAT_NUMBER_START_0 | FLOAT_NUMBER_START_1 | FLOAT_NUMBER_START_2
+        ) {
+            if self.remaining_float_lit_text.is_empty() {
+                always!(
+                    literal.is_some(),
+                    "kind={:?}, cursor tt={:?}",
+                    kind,
+                    self.cursor.token_tree()
+                );
+                let text = literal.map_or(String::new(), |lit| lit.to_string());
+                self.cursor = self.cursor.bump();
+                match text.split_once('.') {
+                    Some((start, end)) => {
+                        self.inner.token(kind, start);
+                        self.remaining_float_lit_text = format!(".{end}");
+                        return;
+                    }
+                    None => {
+                        self.inner.token(kind, &text);
+                        return;
+                    }
+                }
+            } else {
+                self.inner.token(kind, &self.remaining_float_lit_text);
+                self.remaining_float_lit_text.clear();
+                return;
+            }
+        }
+        if kind == DOT && !self.remaining_float_lit_text.is_empty() {
+            always!(self.remaining_float_lit_text.chars().next() == Some('.'));
+            self.inner.token(kind, ".");
+            self.remaining_float_lit_text = self.remaining_float_lit_text[1..].to_string();
+            return;
+        }
+
         let mut last = self.cursor;
         for _ in 0..n_tokens {
             let tmp: u8;


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs b/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs
index 0f606f3cfd..9e4ab043f6 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/tt_conversion.rs
@@ -175,3 +175,33 @@ const _: () = 0e0;
 "#]],
     );
 }
+
+#[test]
+fn float_literal_in_tt() {
+    check(
+        r#"
+macro_rules! constant {
+    ($( $ret:expr; )*) => {};
+}
+
+macro_rules! float_const_impl {
+    () => ( constant!(0.3; 3.3;); );
+}
+
+float_const_impl! {}
+"#,
+        expect![[r#"
+macro_rules! constant {
+    ($( $ret:expr; )*) => {};
+}
+
+macro_rules! float_const_impl {
+    () => ( constant!(0.3; 3.3;); );
+}
+
+constant!(0.3;
+3.3;
+);
+"#]],
+    );
+}