Repository: rust-analyzer/rust-analyzer
Pull request #1793 (2019-09-09 08:41:48)

Fix outer doc-comments of `macro_rules`
Document comments of `macro_rules!` is currently parsed outside the `MACRO_CALL` node,
which makes `DocCommentsOwner::doc_comments()` always empty.

For the input:
```rust
/// Some docs
macro_rules! foo {
    () => {};
}
```

Current parsing tree is:
```
SOURCE_FILE
  COMMENT    // <- This should be children of MACRO_CALL
  WHITESPACE
  MACRO_CALL
    PATH
<...omitted...>
```

It should be:
```
SOURCE_FILE
  MACRO_CALL
    COMMENT
    WHITESPACE
    PATH
<...omitted...>
```



Patch:
diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs
index 74546e5e29..7de422128f 100644
--- a/crates/ra_hir/src/nameres.rs
+++ b/crates/ra_hir/src/nameres.rs
@@ -138,21 +138,7 @@ pub(crate) struct ModuleData {
 #[derive(Debug, Default, PartialEq, Eq, Clone)]
 pub struct ModuleScope {
     items: FxHashMap<Name, Resolution>,
-    /// Macros in current module scoped
-    ///
-    /// This scope works exactly the same way that item scoping does.
-    /// Macro invocation with quantified path will search in it.
-    /// See details below.
     macros: FxHashMap<Name, MacroDef>,
-    /// Macros visable in current module in legacy textual scope
-    ///
-    /// For macros invoked by an unquatified identifier like `bar!()`, `legacy_macros` will be searched in first.
-    /// If it yields no result, then it turns to module scoped `macros`.
-    /// It macros with name quatified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped,
-    /// and only normal scoped `macros` will be searched in.
-    ///
-    /// Note that this automatically inherit macros defined textually before the definition of module itself.
-    legacy_macros: FxHashMap<Name, MacroDef>,
 }
 
 static BUILTIN_SCOPE: Lazy<FxHashMap<Name, Resolution>> = Lazy::new(|| {
@@ -178,7 +164,6 @@ impl ModuleScope {
             _ => None,
         })
     }
-    /// It resolves in module scope. Textual scoped macros are ignored here.
     fn get_item_or_macro(&self, name: &Name) -> Option<ItemOrMacro> {
         match (self.get(name), self.macros.get(name)) {
             (Some(item), _) if !item.def.is_none() => Some(Either::A(item.def)),
@@ -186,9 +171,6 @@ impl ModuleScope {
             _ => None,
         }
     }
-    fn get_legacy_macro(&self, name: &Name) -> Option<MacroDef> {
-        self.legacy_macros.get(name).copied()
-    }
 }
 
 type ItemOrMacro = Either<PerNs<ModuleDef>, MacroDef>;
@@ -502,21 +484,16 @@ impl CrateDefMap {
         name: &Name,
     ) -> ItemOrMacro {
         // Resolve in:
-        //  - legacy scope
         //  - current module / scope
         //  - extern prelude
         //  - std prelude
-        let from_legacy_macro = self[module]
-            .scope
-            .get_legacy_macro(name)
-            .map_or_else(|| Either::A(PerNs::none()), Either::B);
         let from_scope =
             self[module].scope.get_item_or_macro(name).unwrap_or_else(|| Either::A(PerNs::none()));
         let from_extern_prelude =
             self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it));
         let from_prelude = self.resolve_in_prelude(db, name);
 
-        or(from_legacy_macro, or(from_scope, or(Either::A(from_extern_prelude), from_prelude)))
+        or(from_scope, or(Either::A(from_extern_prelude), from_prelude))
     }
 
     fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs<ModuleDef> {
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 09cda76567..1c0d4369e4 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -14,8 +14,8 @@ use crate::{
         raw, CrateDefMap, CrateModuleId, ItemOrMacro, ModuleData, ModuleDef, PerNs,
         ReachedFixedPoint, Resolution, ResolveMode,
     },
-    AstId, Const, Enum, Function, HirFileId, MacroDef, Module, Name, Path, PathKind, Static,
-    Struct, Trait, TypeAlias, Union,
+    AstId, Const, Enum, Function, HirFileId, MacroDef, Module, Name, Path, Static, Struct, Trait,
+    TypeAlias, Union,
 };
 
 pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap {
@@ -40,6 +40,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
         glob_imports: FxHashMap::default(),
         unresolved_imports: Vec::new(),
         unexpanded_macros: Vec::new(),
+        global_macro_scope: FxHashMap::default(),
         macro_stack_monitor: MacroStackMonitor::default(),
     };
     collector.collect();
@@ -81,6 +82,7 @@ struct DefCollector<DB> {
     glob_imports: FxHashMap<CrateModuleId, Vec<(CrateModuleId, raw::ImportId)>>,
     unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>,
     unexpanded_macros: Vec<(CrateModuleId, AstId<ast::MacroCall>, Path)>,
+    global_macro_scope: FxHashMap<Name, MacroDefId>,
 
     /// Some macro use `$tt:tt which mean we have to handle the macro perfectly
     /// To prevent stack overflow, we add a deep counter here for prevent that.
@@ -134,6 +136,20 @@ where
         macro_id: MacroDefId,
         export: bool,
     ) {
+        // macro-by-example in Rust have completely weird name resolution logic,
+        // unlike anything else in the language. We'd don't fully implement yet,
+        // just give a somewhat precise approximation.
+        //
+        // Specifically, we store a set of visible macros in each module, just
+        // like how we do with usual items. This is wrong, however, because
+        // macros can be shadowed and their scopes are mostly unrelated to
+        // modules. To paper over the second problem, we also maintain
+        // `global_macro_scope` which works when we construct `CrateDefMap`, but
+        // is completely ignored in expressions.
+        //
+        // What we should do is that, in CrateDefMap, we should maintain a
+        // separate tower of macro scopes, with ids. Then, for each item in the
+        // module, we need to store it's macro scope.
         let def = Either::B(MacroDef { id: macro_id });
 
         // In Rust, `#[macro_export]` macros are unconditionally visible at the
@@ -146,29 +162,13 @@ where
             self.def_map.exported_macros.insert(name.clone(), macro_id);
         }
         self.update(module_id, None, &[(name.clone(), def)]);
-        self.define_legacy_macro(module_id, name.clone(), macro_id);
-    }
-
-    /// Define a legacy textual scoped macro in module
-    ///
-    /// We use a map `legacy_macros` to store all legacy textual scoped macros visable per module.
-    /// It will clone all macros from parent legacy scope, whose definition is prior to
-    /// the definition of current module.
-    /// And also, `macro_use` on a module will import all legacy macros visable inside to
-    /// current legacy scope, with possible shadowing.
-    fn define_legacy_macro(&mut self, module_id: CrateModuleId, name: Name, macro_id: MacroDefId) {
-        // Always shadowing
-        self.def_map.modules[module_id].scope.legacy_macros.insert(name, MacroDef { id: macro_id });
+        self.global_macro_scope.insert(name, macro_id);
     }
 
     /// Import macros from `#[macro_use] extern crate`.
     ///
     /// They are non-scoped, and will only be inserted into mutable `global_macro_scope`.
-    fn import_macros_from_extern_crate(
-        &mut self,
-        current_module_id: CrateModuleId,
-        import: &raw::ImportData,
-    ) {
+    fn import_macros_from_extern_crate(&mut self, import: &raw::ImportData) {
         log::debug!(
             "importing macros from extern crate: {:?} ({:?})",
             import,
@@ -184,14 +184,14 @@ where
 
         if let Some(ModuleDef::Module(m)) = res.take_types() {
             tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use);
-            self.import_all_macros_exported(current_module_id, m);
+            self.import_all_macros_exported(m);
         }
     }
 
-    fn import_all_macros_exported(&mut self, current_module_id: CrateModuleId, module: Module) {
+    fn import_all_macros_exported(&mut self, module: Module) {
         let item_map = self.db.crate_def_map(module.krate);
         for (name, &macro_id) in &item_map.exported_macros {
-            self.define_legacy_macro(current_module_id, name.clone(), macro_id);
+            self.global_macro_scope.insert(name.clone(), macro_id);
         }
     }
 
@@ -528,7 +528,7 @@ where
         if let Some(prelude_module) = self.def_collector.def_map.prelude {
             if prelude_module.krate != self.def_collector.def_map.krate {
                 tested_by!(prelude_is_macro_use);
-                self.def_collector.import_all_macros_exported(self.module_id, prelude_module);
+                self.def_collector.import_all_macros_exported(prelude_module);
             }
         }
 
@@ -539,7 +539,7 @@ where
             if let raw::RawItem::Import(import_id) = *item {
                 let import = self.raw_items[import_id].clone();
                 if import.is_extern_crate && import.is_macro_use {
-                    self.def_collector.import_macros_from_extern_crate(self.module_id, &import);
+                    self.def_collector.import_macros_from_extern_crate(&import);
                 }
             }
         }
@@ -561,11 +561,10 @@ where
     fn collect_module(&mut self, module: &raw::ModuleData) {
         match module {
             // inline module, just recurse
-            raw::ModuleData::Definition { name, items, ast_id, attr_path, is_macro_use } => {
+            raw::ModuleData::Definition { name, items, ast_id, attr_path } => {
                 let module_id =
                     self.push_child_module(name.clone(), ast_id.with_file_id(self.file_id), None);
                 let parent_module = ParentModule { name, attr_path: attr_path.as_ref() };
-
                 ModCollector {
                     def_collector: &mut *self.def_collector,
                     module_id,
@@ -574,12 +573,9 @@ where
                     parent_module: Some(parent_module),
                 }
                 .collect(&*items);
-                if *is_macro_use {
-                    self.import_all_legacy_macros(module_id);
-                }
             }
             // out of line module, resolve, parse and recurse
-            raw::ModuleData::Declaration { name, ast_id, attr_path, is_macro_use } => {
+            raw::ModuleData::Declaration { name, ast_id, attr_path } => {
                 let ast_id = ast_id.with_file_id(self.file_id);
                 let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none();
                 match resolve_submodule(
@@ -600,10 +596,7 @@ where
                             raw_items: &raw_items,
                             parent_module: None,
                         }
-                        .collect(raw_items.items());
-                        if *is_macro_use {
-                            self.import_all_legacy_macros(module_id);
-                        }
+                        .collect(raw_items.items())
                     }
                     Err(candidate) => self.def_collector.def_map.diagnostics.push(
                         DefDiagnostic::UnresolvedModule {
@@ -628,7 +621,6 @@ where
         modules[res].parent = Some(self.module_id);
         modules[res].declaration = Some(declaration);
         modules[res].definition = definition;
-        modules[res].scope.legacy_macros = modules[self.module_id].scope.legacy_macros.clone();
         modules[self.module_id].children.insert(name.clone(), res);
         let resolution = Resolution {
             def: PerNs::types(
@@ -682,32 +674,20 @@ where
 
         let ast_id = mac.ast_id.with_file_id(self.file_id);
 
-        // Case 2: try to resolve in legacy scope and expand macro_rules, triggering
+        // Case 2: try to expand macro_rules from this crate, triggering
         // recursive item collection.
-        if let Some(macro_def) = mac.path.as_ident().and_then(|name| {
-            self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
-        }) {
-            let def = macro_def.id;
+        if let Some(macro_id) =
+            mac.path.as_ident().and_then(|name| self.def_collector.global_macro_scope.get(&name))
+        {
+            let def = *macro_id;
             let macro_call_id = MacroCallLoc { def, ast_id }.id(self.def_collector.db);
 
             self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, def);
             return;
         }
 
-        // Case 3: resolve in module scope, expand during name resolution.
-        // We rewrite simple path `macro_name` to `self::macro_name` to force resolve in module scope only.
-        let mut path = mac.path.clone();
-        if path.is_ident() {
-            path.kind = PathKind::Self_;
-        }
-        self.def_collector.unexpanded_macros.push((self.module_id, ast_id, path));
-    }
-
-    fn import_all_legacy_macros(&mut self, module_id: CrateModuleId) {
-        let macros = self.def_collector.def_map[module_id].scope.legacy_macros.clone();
-        for (name, macro_) in macros {
-            self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_.id);
-        }
+        // Case 3: path to a macro from another crate, expand during name resolution
+        self.def_collector.unexpanded_macros.push((self.module_id, ast_id, mac.path.clone()))
     }
 }
 
@@ -735,6 +715,7 @@ mod tests {
             glob_imports: FxHashMap::default(),
             unresolved_imports: Vec::new(),
             unexpanded_macros: Vec::new(),
+            global_macro_scope: FxHashMap::default(),
             macro_stack_monitor: monitor,
         };
         collector.collect();
diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs
index c646d3d005..7963736e0d 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -134,14 +134,12 @@ pub(super) enum ModuleData {
         name: Name,
         ast_id: FileAstId<ast::Module>,
         attr_path: Option<SmolStr>,
-        is_macro_use: bool,
     },
     Definition {
         name: Name,
         ast_id: FileAstId<ast::Module>,
         items: Vec<RawItem>,
         attr_path: Option<SmolStr>,
-        is_macro_use: bool,
     },
 }
 
@@ -269,15 +267,10 @@ impl RawItemsCollector {
         };
 
         let ast_id = self.source_ast_id_map.ast_id(&module);
-        let is_macro_use = module.has_atom_attr("macro_use");
         if module.has_semi() {
             let attr_path = extract_mod_path_attribute(&module);
-            let item = self.raw_items.modules.alloc(ModuleData::Declaration {
-                name,
-                ast_id,
-                attr_path,
-                is_macro_use,
-            });
+            let item =
+                self.raw_items.modules.alloc(ModuleData::Declaration { name, ast_id, attr_path });
             self.push_item(current_module, RawItem::Module(item));
             return;
         }
@@ -289,7 +282,6 @@ impl RawItemsCollector {
                 ast_id,
                 items: Vec::new(),
                 attr_path,
-                is_macro_use,
             });
             self.process_module(Some(item), item_list);
             self.push_item(current_module, RawItem::Module(item));
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs
index f59c559e95..be6e517803 100644
--- a/crates/ra_syntax/src/parsing/text_tree_sink.rs
+++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs
@@ -144,8 +144,8 @@ fn n_attached_trivias<'a>(
     trivias: impl Iterator<Item = (SyntaxKind, &'a str)>,
 ) -> usize {
     match kind {
-        CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF | TRAIT_DEF
-        | MODULE | RECORD_FIELD_DEF => {
+        MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF
+        | TRAIT_DEF | MODULE | RECORD_FIELD_DEF => {
             let mut res = 0;
             for (i, (kind, text)) in trivias.enumerate() {
                 match kind {
diff --git a/docs/user/README.md b/docs/user/README.md
index 8205fa4046..c11e0fcb6d 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -106,14 +106,13 @@ to load path and require it in `init.el`
   - Note that if you use a plugin manager other than `vim-plug`, you may need to manually
     checkout the `release` branch wherever your plugin manager cloned it. Otherwise you will
     get errors about a missing javascript file.
-* Run `:CocInstall coc-rust-analyzer` to install [coc-rust-analyzer], this extension implemented _most_ of the features supported in the VSCode extension:
+* Run `:CocInstall coc-rust-analyzer` to install `coc-rust-analyzer`, this extension already implemented _almost_ all features supported in VSCode extension:
   - same configurations as VSCode extension, `rust-analyzer.raLspServerPath`, `rust-analyzer.enableCargoWatchOnStartup` etc.
   - same commands too, `rust-analyzer.analyzerStatus`, `rust-analyzer.startCargoWatch` etc.
-  - highlighting and inlay_hints are not implemented yet
+  - highlighting and inlay_hints is not works by now
 
 [coc.nvim]: https://github.com/neoclide/coc.nvim
 [coc-vim-conf]: https://github.com/neoclide/coc.nvim/#example-vim-configuration
-[coc-rust-analyzer]: https://github.com/fannheyward/coc-rust-analyzer
 
 
 ## Sublime Text 3


Test patch:
diff --git a/crates/ra_hir/src/nameres/tests/macros.rs b/crates/ra_hir/src/nameres/tests/macros.rs
index ebc4d6890b..c62152d26a 100644
--- a/crates/ra_hir/src/nameres/tests/macros.rs
+++ b/crates/ra_hir/src/nameres/tests/macros.rs
@@ -268,114 +268,12 @@ fn prelude_cycle() {
     );
     assert_snapshot!(map, @r###"
         ⋮crate
+        ⋮foo: t
         ⋮prelude: t
         ⋮
         ⋮crate::prelude
         ⋮declare_mod: m
-    "###);
-}
-
-#[test]
-fn plain_macros_are_legacy_textual_scoped() {
-    let map = def_map(
-        r#"
-        //- /main.rs
-        mod m1;
-        bar!(NotFoundNotMacroUse);
-
-        mod m2 {
-            foo!(NotFoundBeforeInside2);
-        }
-
-        macro_rules! foo {
-            ($x:ident) => { struct $x; }
-        }
-        foo!(Ok);
-
-        mod m3;
-        foo!(OkShadowStop);
-        bar!(NotFoundMacroUseStop);
-
-        #[macro_use]
-        mod m5 {
-            #[macro_use]
-            mod m6 {
-                macro_rules! foo {
-                    ($x:ident) => { fn $x() {} }
-                }
-            }
-        }
-        foo!(ok_double_macro_use_shadow);
-
-        baz!(NotFoundBefore);
-        #[macro_use]
-        mod m7 {
-            macro_rules! baz {
-                ($x:ident) => { struct $x; }
-            }
-        }
-        baz!(OkAfter);
-
-        //- /m1.rs
-        foo!(NotFoundBeforeInside1);
-        macro_rules! bar {
-            ($x:ident) => { struct $x; }
-        }
-
-        //- /m3/mod.rs
-        foo!(OkAfterInside);
-        macro_rules! foo {
-            ($x:ident) => { fn $x() {} }
-        }
-        foo!(ok_shadow);
-
-        #[macro_use]
-        mod m4;
-        bar!(OkMacroUse);
-
-        //- /m3/m4.rs
-        foo!(ok_shadow_deep);
-        macro_rules! bar {
-            ($x:ident) => { struct $x; }
-        }
-        "#,
-    );
-    assert_snapshot!(map, @r###"
-   ⋮crate
-   ⋮Ok: t v
-   ⋮OkAfter: t v
-   ⋮OkShadowStop: t v
-   ⋮foo: m
-   ⋮m1: t
-   ⋮m2: t
-   ⋮m3: t
-   ⋮m5: t
-   ⋮m7: t
-   ⋮ok_double_macro_use_shadow: v
-   ⋮
-   ⋮crate::m7
-   ⋮baz: m
-   ⋮
-   ⋮crate::m1
-   ⋮bar: m
-   ⋮
-   ⋮crate::m5
-   ⋮m6: t
-   ⋮
-   ⋮crate::m5::m6
-   ⋮foo: m
-   ⋮
-   ⋮crate::m2
-   ⋮
-   ⋮crate::m3
-   ⋮OkAfterInside: t v
-   ⋮OkMacroUse: t v
-   ⋮foo: m
-   ⋮m4: t
-   ⋮ok_shadow: v
-   ⋮
-   ⋮crate::m3::m4
-   ⋮bar: m
-   ⋮ok_shadow_deep: v
+        ⋮
+        ⋮crate::foo
     "###);
 }
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 25716fe8c1..c4bddde85d 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -2803,41 +2803,6 @@ fn main() {
     );
 }
 
-#[test]
-fn infer_legacy_textual_scoped_macros_expanded() {
-    assert_snapshot!(
-        infer(r#"
-struct Foo(Vec<i32>);
-
-#[macro_use]
-mod m {
-    macro_rules! foo {
-        ($($item:expr),*) => {
-            {
-                Foo(vec![$($item,)*])
-            }
-        };
-    }
-}
-
-fn main() {
-    let x = foo!(1,2);
-    let y = crate::foo!(1,2);
-}
-"#),
-        @r###"
-    ![0; 17) '{Foo(v...,2,])}': Foo
-    ![1; 4) 'Foo': Foo({unknown}) -> Foo
-    ![1; 16) 'Foo(vec![1,2,])': Foo
-    ![5; 15) 'vec![1,2,]': {unknown}
-    [195; 251) '{     ...,2); }': ()
-    [205; 206) 'x': Foo
-    [228; 229) 'y': {unknown}
-    [232; 248) 'crate:...!(1,2)': {unknown}
-    "###
-    );
-}
-
 #[ignore]
 #[test]
 fn method_resolution_trait_before_autoref() {
diff --git a/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rs b/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rs
new file mode 100644
index 0000000000..b59c23c56a
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rs
@@ -0,0 +1,5 @@
+/// Some docs
+#[macro_export]
+macro_rules! foo {
+    () => {};
+}
diff --git a/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.txt b/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.txt
new file mode 100644
index 0000000000..f77221f76f
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0053_outer_attribute_on_macro_rules.txt
@@ -0,0 +1,37 @@
+SOURCE_FILE@[0; 65)
+  MACRO_CALL@[0; 64)
+    COMMENT@[0; 13) "/// Some docs"
+    WHITESPACE@[13; 14) "\n"
+    ATTR@[14; 29)
+      POUND@[14; 15) "#"
+      TOKEN_TREE@[15; 29)
+        L_BRACK@[15; 16) "["
+        IDENT@[16; 28) "macro_export"
+        R_BRACK@[28; 29) "]"
+    WHITESPACE@[29; 30) "\n"
+    PATH@[30; 41)
+      PATH_SEGMENT@[30; 41)
+        NAME_REF@[30; 41)
+          IDENT@[30; 41) "macro_rules"
+    EXCL@[41; 42) "!"
+    WHITESPACE@[42; 43) " "
+    NAME@[43; 46)
+      IDENT@[43; 46) "foo"
+    WHITESPACE@[46; 47) " "
+    TOKEN_TREE@[47; 64)
+      L_CURLY@[47; 48) "{"
+      WHITESPACE@[48; 53) "\n    "
+      TOKEN_TREE@[53; 55)
+        L_PAREN@[53; 54) "("
+        R_PAREN@[54; 55) ")"
+      WHITESPACE@[55; 56) " "
+      EQ@[56; 57) "="
+      R_ANGLE@[57; 58) ">"
+      WHITESPACE@[58; 59) " "
+      TOKEN_TREE@[59; 61)
+        L_CURLY@[59; 60) "{"
+        R_CURLY@[60; 61) "}"
+      SEMI@[61; 62) ";"
+      WHITESPACE@[62; 63) "\n"
+      R_CURLY@[63; 64) "}"
+  WHITESPACE@[64; 65) "\n"