Repository: rust-analyzer/rust-analyzer
Pull request #8201 (2021-03-25 20:04:24)

Fix recursive macro statements expansion
This PR attempts to properly handle macro statement expansion by implementing the following:

1.  Merge macro expanded statements to parent scope statements.
2.  Add a new hir `Expr::MacroStmts` for handle tail expression infer.

PS : The scope of macro expanded statements are so strange that it took more time than I thought to understand and implement it :(

Fixes  #8171




Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b36bca0aba..7f666ca618 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,7 +1,5 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
-
 [[package]]
 name = "addr2line"
 version = "0.14.1"
@@ -49,9 +47,9 @@ checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
 
 [[package]]
 name = "arrayvec"
-version = "0.6.0"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "682f88bd1270f264991da8922b89ee1fb520b0da73f97c9f73cda54980123017"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
 [[package]]
 name = "atty"
diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml
index 2ef5bcbc95..55e9c3f0cf 100644
--- a/crates/hir/Cargo.toml
+++ b/crates/hir/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
 log = "0.4.8"
 rustc-hash = "1.1.0"
 either = "1.5.3"
-arrayvec = "0.6"
+arrayvec = "0.5.1"
 itertools = "0.10.0"
 smallvec = "1.4.0"
 
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 05a60e1586..6fa676c4d2 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2238,7 +2238,7 @@ pub enum ScopeDef {
 }
 
 impl ScopeDef {
-    pub fn all_items(def: PerNs) -> ArrayVec<Self, 3> {
+    pub fn all_items(def: PerNs) -> ArrayVec<[Self; 3]> {
         let mut items = ArrayVec::new();
 
         match (def.take_types(), def.take_values()) {
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 19f5065d13..229e81dd43 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -74,6 +74,7 @@ pub(super) fn lower(
             _c: Count::new(),
         },
         expander,
+        statements_in_scope: Vec::new(),
     }
     .collect(params, body)
 }
@@ -83,6 +84,7 @@ struct ExprCollector<'a> {
     expander: Expander,
     body: Body,
     source_map: BodySourceMap,
+    statements_in_scope: Vec<Statement>,
 }
 
 impl ExprCollector<'_> {
@@ -533,15 +535,13 @@ impl ExprCollector<'_> {
                 ids[0]
             }
             ast::Expr::MacroStmts(e) => {
-                // FIXME:  these statements should be held by some hir containter
-                for stmt in e.statements() {
-                    self.collect_stmt(stmt);
-                }
-                if let Some(expr) = e.expr() {
-                    self.collect_expr(expr)
-                } else {
-                    self.alloc_expr(Expr::Missing, syntax_ptr)
-                }
+                e.statements().for_each(|s| self.collect_stmt(s));
+                let tail = e
+                    .expr()
+                    .map(|e| self.collect_expr(e))
+                    .unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()));
+
+                self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr)
             }
         })
     }
@@ -618,58 +618,54 @@ impl ExprCollector<'_> {
         }
     }
 
-    fn collect_stmt(&mut self, s: ast::Stmt) -> Option<Vec<Statement>> {
-        let stmt = match s {
+    fn collect_stmt(&mut self, s: ast::Stmt) {
+        match s {
             ast::Stmt::LetStmt(stmt) => {
-                self.check_cfg(&stmt)?;
-
+                if self.check_cfg(&stmt).is_none() {
+                    return;
+                }
                 let pat = self.collect_pat_opt(stmt.pat());
                 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
                 let initializer = stmt.initializer().map(|e| self.collect_expr(e));
-                vec![Statement::Let { pat, type_ref, initializer }]
+                self.statements_in_scope.push(Statement::Let { pat, type_ref, initializer });
             }
             ast::Stmt::ExprStmt(stmt) => {
-                self.check_cfg(&stmt)?;
+                if self.check_cfg(&stmt).is_none() {
+                    return;
+                }
 
                 // Note that macro could be expended to multiple statements
                 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
                     let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
-                    let mut stmts = vec![];
 
                     self.collect_macro_call(m, syntax_ptr.clone(), false, |this, expansion| {
                         match expansion {
                             Some(expansion) => {
                                 let statements: ast::MacroStmts = expansion;
 
-                                statements.statements().for_each(|stmt| {
-                                    if let Some(mut r) = this.collect_stmt(stmt) {
-                                        stmts.append(&mut r);
-                                    }
-                                });
+                                statements.statements().for_each(|stmt| this.collect_stmt(stmt));
                                 if let Some(expr) = statements.expr() {
-                                    stmts.push(Statement::Expr(this.collect_expr(expr)));
+                                    let expr = this.collect_expr(expr);
+                                    this.statements_in_scope.push(Statement::Expr(expr));
                                 }
                             }
                             None => {
-                                stmts.push(Statement::Expr(
-                                    this.alloc_expr(Expr::Missing, syntax_ptr.clone()),
-                                ));
+                                let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
+                                this.statements_in_scope.push(Statement::Expr(expr));
                             }
                         }
                     });
-                    stmts
                 } else {
-                    vec![Statement::Expr(self.collect_expr_opt(stmt.expr()))]
+                    let expr = self.collect_expr_opt(stmt.expr());
+                    self.statements_in_scope.push(Statement::Expr(expr));
                 }
             }
             ast::Stmt::Item(item) => {
-                self.check_cfg(&item)?;
-
-                return None;
+                if self.check_cfg(&item).is_none() {
+                    return;
+                }
             }
-        };
-
-        Some(stmt)
+        }
     }
 
     fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
@@ -685,10 +681,12 @@ impl ExprCollector<'_> {
         let module = if has_def_map { def_map.root() } else { self.expander.module };
         let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
         let prev_local_module = mem::replace(&mut self.expander.module, module);
+        let prev_statements = std::mem::take(&mut self.statements_in_scope);
+
+        block.statements().for_each(|s| self.collect_stmt(s));
 
-        let statements =
-            block.statements().filter_map(|s| self.collect_stmt(s)).flatten().collect();
         let tail = block.tail_expr().map(|e| self.collect_expr(e));
+        let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements);
         let syntax_node_ptr = AstPtr::new(&block.into());
         let expr_id = self.alloc_expr(
             Expr::Block { id: block_id, statements, tail, label: None },
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 24be937739..6c7376fad3 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -171,6 +171,9 @@ pub enum Expr {
     Unsafe {
         body: ExprId,
     },
+    MacroStmts {
+        tail: ExprId,
+    },
     Array(Array),
     Literal(Literal),
 }
@@ -357,6 +360,7 @@ impl Expr {
                     f(*repeat)
                 }
             },
+            Expr::MacroStmts { tail } => f(*tail),
             Expr::Literal(_) => {}
         }
     }
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index ae2475b4e4..ca0048b163 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -110,15 +110,6 @@ impl ItemTree {
                     // still need to collect inner items.
                     ctx.lower_inner_items(e.syntax())
                 },
-                ast::ExprStmt(stmt) => {
-                    // Macros can expand to stmt. We return an empty item tree in this case, but
-                    // still need to collect inner items.
-                    ctx.lower_inner_items(stmt.syntax())
-                },
-                ast::Item(item) => {
-                    // Macros can expand to stmt and other item, and we add it as top level item
-                    ctx.lower_single_item(item)
-                },
                 _ => {
                     panic!("cannot create item tree from {:?} {}", syntax, syntax);
                 },
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index d3fe1ce1eb..3f558edd81 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -87,14 +87,6 @@ impl Ctx {
         self.tree
     }
 
-    pub(super) fn lower_single_item(mut self, item: ast::Item) -> ItemTree {
-        self.tree.top_level = self
-            .lower_mod_item(&item, false)
-            .map(|item| item.0)
-            .unwrap_or_else(|| Default::default());
-        self.tree
-    }
-
     pub(super) fn lower_inner_items(mut self, within: &SyntaxNode) -> ItemTree {
         self.collect_inner_items(within);
         self.tree
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index fc73e435bf..d672f67238 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -5,7 +5,13 @@ use std::sync::Arc;
 use base_db::{salsa, SourceDatabase};
 use mbe::{ExpandError, ExpandResult, MacroRules};
 use parser::FragmentKind;
-use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
+use syntax::{
+    algo::diff,
+    ast::{MacroStmts, NameOwner},
+    AstNode, GreenNode, Parse,
+    SyntaxKind::*,
+    SyntaxNode,
+};
 
 use crate::{
     ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander,
@@ -340,13 +346,19 @@ fn parse_macro_with_arg(
         None => return ExpandResult { value: None, err: result.err },
     };
 
-    log::debug!("expanded = {}", tt.as_debug_string());
-
     let fragment_kind = to_fragment_kind(db, macro_call_id);
 
+    log::debug!("expanded = {}", tt.as_debug_string());
+    log::debug!("kind = {:?}", fragment_kind);
+
     let (parse, rev_token_map) = match mbe::token_tree_to_syntax_node(&tt, fragment_kind) {
         Ok(it) => it,
         Err(err) => {
+            log::debug!(
+                "failed to parse expanstion to {:?} = {}",
+                fragment_kind,
+                tt.as_debug_string()
+            );
             return ExpandResult::only_err(err);
         }
     };
@@ -362,15 +374,34 @@ fn parse_macro_with_arg(
                     return ExpandResult::only_err(err);
                 }
             };
-
-            if !diff(&node, &call_node.value).is_empty() {
-                ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: Some(err) }
-            } else {
+            if is_self_replicating(&node, &call_node.value) {
                 return ExpandResult::only_err(err);
+            } else {
+                ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: Some(err) }
+            }
+        }
+        None => {
+            log::debug!("parse = {:?}", parse.syntax_node().kind());
+            ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: None }
+        }
+    }
+}
+
+fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
+    if diff(from, to).is_empty() {
+        return true;
+    }
+    if let Some(stmts) = MacroStmts::cast(from.clone()) {
+        if stmts.statements().any(|stmt| diff(stmt.syntax(), to).is_empty()) {
+            return true;
+        }
+        if let Some(expr) = stmts.expr() {
+            if diff(expr.syntax(), to).is_empty() {
+                return true;
             }
         }
-        None => ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: None },
     }
+    false
 }
 
 fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
@@ -390,21 +421,15 @@ fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
 
     let parent = match syn.parent() {
         Some(it) => it,
-        None => {
-            // FIXME:
-            // If it is root, which means the parent HirFile
-            // MacroKindFile must be non-items
-            // return expr now.
-            return FragmentKind::Expr;
-        }
+        None => return FragmentKind::Statements,
     };
 
     match parent.kind() {
         MACRO_ITEMS | SOURCE_FILE => FragmentKind::Items,
-        MACRO_STMTS => FragmentKind::Statement,
+        MACRO_STMTS => FragmentKind::Statements,
         ITEM_LIST => FragmentKind::Items,
         LET_STMT => {
-            // FIXME: Handle Pattern
+            // FIXME: Handle LHS Pattern
             FragmentKind::Expr
         }
         EXPR_STMT => FragmentKind::Statements,
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 030b7eebed..0ef27cd373 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
 [dependencies]
 cov-mark = { version = "1.1", features = ["thread-local"] }
 itertools = "0.10.0"
-arrayvec = "0.6"
+arrayvec = "0.5.1"
 smallvec = "1.2.0"
 ena = "0.14.0"
 log = "0.4.8"
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 51480304b4..6149067c73 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -1,7 +1,8 @@
 //! FIXME: write short doc here
 
-use std::{array, fmt};
+use std::fmt;
 
+use arrayvec::ArrayVec;
 use chalk_ir::Mutability;
 use hir_def::{
     db::DefDatabase,
@@ -668,7 +669,8 @@ fn fn_traits(db: &dyn DefDatabase, trait_: TraitId) -> impl Iterator<Item = Trai
         db.lang_item(krate, "fn_mut".into()),
         db.lang_item(krate, "fn_once".into()),
     ];
-    array::IntoIter::new(fn_traits).into_iter().flatten().flat_map(|it| it.as_trait())
+    // FIXME: Replace ArrayVec when into_iter is a thing on arrays
+    ArrayVec::from(fn_traits).into_iter().flatten().flat_map(|it| it.as_trait())
 }
 
 pub fn write_bounds_like_dyn_trait_with_prefix(
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 3f3187ea27..e6ede05ca1 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -767,6 +767,7 @@ impl<'a> InferenceContext<'a> {
                     None => self.table.new_float_var(),
                 },
             },
+            Expr::MacroStmts { tail } => self.infer_expr(*tail, expected),
         };
         // use a new type variable if we got unknown here
         let ty = self.insert_type_vars_shallow(ty);
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index bf7d5eded2..84d9a1e186 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -228,12 +228,12 @@ impl Ty {
         &self,
         db: &dyn HirDatabase,
         cur_crate: CrateId,
-    ) -> Option<ArrayVec<CrateId, 2>> {
+    ) -> Option<ArrayVec<[CrateId; 2]>> {
         // Types like slice can have inherent impls in several crates, (core and alloc).
         // The corresponding impls are marked with lang items, so we can use them to find the required crates.
         macro_rules! lang_item_crate {
             ($($name:expr),+ $(,)?) => {{
-                let mut v = ArrayVec::<LangItemTarget, 2>::new();
+                let mut v = ArrayVec::<[LangItemTarget; 2]>::new();
                 $(
                     v.extend(db.lang_item(cur_crate, $name.into()));
                 )+
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 9f01acc264..0c3fec3c7f 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -16,7 +16,7 @@ itertools = "0.10.0"
 rowan = "0.13.0-pre.3"
 rustc_lexer = { version = "710.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
-arrayvec = "0.6"
+arrayvec = "0.5.1"
 once_cell = "1.3.1"
 indexmap = "1.4.0"
 smol_str = { version = "0.1.15", features = ["serde"] }
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4f..18820786a5 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -2,7 +2,7 @@
 //! immutable, all function here return a fresh copy of the tree, instead of
 //! doing an in-place modification.
 use std::{
-    array, fmt, iter,
+    fmt, iter,
     ops::{self, RangeInclusive},
 };
 
@@ -32,7 +32,7 @@ impl ast::BinExpr {
 impl ast::Fn {
     #[must_use]
     pub fn with_body(&self, body: ast::BlockExpr) -> ast::Fn {
-        let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
+        let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
         let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
             old_body.syntax().clone().into()
         } else if let Some(semi) = self.semicolon_token() {
@@ -55,8 +55,9 @@ impl ast::Fn {
 
         let anchor = self.name().expect("The function must have a name").syntax().clone();
 
-        let to_insert = [generic_args.syntax().clone().into()];
-        self.insert_children(InsertPosition::After(anchor.into()), array::IntoIter::new(to_insert))
+        let mut to_insert: ArrayVec<[SyntaxElement; 1]> = ArrayVec::new();
+        to_insert.push(generic_args.syntax().clone().into());
+        self.insert_children(InsertPosition::After(anchor.into()), to_insert)
     }
 }
 
@@ -95,7 +96,7 @@ where
 impl ast::Impl {
     #[must_use]
     pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
-        let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
+        let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
         if let Some(old_items) = self.assoc_item_list() {
             let to_replace: SyntaxElement = old_items.syntax().clone().into();
             to_insert.push(items.syntax().clone().into());
@@ -140,7 +141,7 @@ impl ast::AssocItemList {
             },
         };
         let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
-        let to_insert: ArrayVec<SyntaxElement, 2> =
+        let to_insert: ArrayVec<[SyntaxElement; 2]> =
             [ws.ws().into(), item.syntax().clone().into()].into();
         self.insert_children(position, to_insert)
     }
@@ -191,7 +192,7 @@ impl ast::RecordExprFieldList {
             tokens::single_space()
         };
 
-        let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
+        let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
         to_insert.push(space.into());
         to_insert.push(field.syntax().clone().into());
         to_insert.push(make::token(T![,]).into());
@@ -304,7 +305,7 @@ impl ast::PathSegment {
                 iter::once(type_args.syntax().clone().into()),
             );
         }
-        let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
+        let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
         if turbo {
             to_insert.push(make::token(T![::]).into());
         }
@@ -443,7 +444,7 @@ impl ast::MatchArmList {
         let arm_ws = tokens::WsBuilder::new("    ");
         let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
         let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
-        let to_insert: ArrayVec<SyntaxElement, 3> =
+        let to_insert: ArrayVec<[SyntaxElement; 3]> =
             [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
         self.insert_children(position, to_insert)
     }
@@ -464,7 +465,7 @@ impl ast::GenericParamList {
     pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
         let space = tokens::single_space();
 
-        let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
+        let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
         if self.generic_params().next().is_some() {
             to_insert.push(space.into());
         }


Test patch:
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index 7eda518663..01935ec994 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -226,11 +226,48 @@ fn expr_macro_expanded_in_stmts() {
         "#,
         expect![[r#"
             !0..8 'leta=();': ()
+            !0..8 'leta=();': ()
+            !3..4 'a': ()
+            !5..7 '()': ()
             57..84 '{     ...); } }': ()
         "#]],
     );
 }
 
+#[test]
+fn recurisve_macro_expanded_in_stmts() {
+    check_infer(
+        r#"
+        macro_rules! ng {
+            ([$($tts:tt)*]) => {
+                $($tts)*;
+            };
+            ([$($tts:tt)*] $head:tt $($rest:tt)*) => {
+                ng! {
+                    [$($tts)* $head] $($rest)*
+                }
+            };
+        }
+        fn foo() {
+            ng!([] let a = 3);
+            let b = a;
+        }
+        "#,
+        expect![[r#"
+            !0..7 'leta=3;': {unknown}
+            !0..7 'leta=3;': {unknown}
+            !0..13 'ng!{[leta=3]}': {unknown}
+            !0..13 'ng!{[leta=]3}': {unknown}
+            !0..13 'ng!{[leta]=3}': {unknown}
+            !3..4 'a': i32
+            !5..6 '3': i32
+            196..237 '{     ...= a; }': ()
+            229..230 'b': i32
+            233..234 'a': i32
+        "#]],
+    );
+}
+
 #[test]
 fn recursive_inner_item_macro_rules() {
     check_infer(
@@ -246,7 +283,8 @@ fn recursive_inner_item_macro_rules() {
         "#,
         expect![[r#"
             !0..1 '1': i32
-            !0..7 'mac!($)': {unknown}
+            !0..26 'macro_...>{1};}': {unknown}
+            !0..26 'macro_...>{1};}': {unknown}
             107..143 '{     ...!(); }': ()
             129..130 'a': i32
         "#]],