diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index d02556b5cdf..7b5e783e4ed 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -342,7 +342,7 @@ pub fn parse_trees_top(input: TokenStream) -> Result { } Ok(quote! { { - let mut __nodes: Vec = Vec::new(); + let mut __nodes: Vec = Vec::new(); #(#items)* __nodes } @@ -356,7 +356,7 @@ fn parse_direct_node(tokens: &mut Tokens, ctx: &Ident) -> Result { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => { let group = expect_group(tokens, Delimiter::Brace)?; let expr = group.stream(); - Ok(quote! { ::std::convert::Into::::into({ #expr }) }) + Ok(quote! { ::std::convert::Into::::into({ #expr }) }) } Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => { let group = expect_group(tokens, Delimiter::Parenthesis)?; @@ -450,7 +450,7 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result::into) + { #expr }.into_iter().map(::std::convert::Into::::into) } } else { let expr = group.stream(); @@ -458,7 +458,7 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result = #chained.collect(); + let #temp: Vec = #chained.collect(); }); // An empty splice means the field is absent — skip it // entirely rather than emitting an empty named field. @@ -471,7 +471,7 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result Result)> = Vec::new(); + let mut __fields: Vec<(&str, Vec)> = Vec::new(); #(#field_args)* #ctx.node(#kind_str, __fields) } @@ -499,7 +499,7 @@ fn parse_direct_node_inner(tokens: &mut Tokens, ctx: &Ident) -> Result template) -- iterator map: produces Vec +/// .map(param -> template) -- iterator map: produces Vec /// ``` /// /// The chain may be empty (returns `base` unchanged). Multiple chained calls @@ -558,10 +558,10 @@ fn parse_chain_suffix(tokens: &mut Tokens, ctx: &Ident, base: TokenStream) -> Re current = quote! { { let mut __iter = #current; - let __result: Option = if let Some(#init_param) = __iter.next() { - let mut __acc: usize = #init_body; + let __result: Option = if let Some(#init_param) = __iter.next() { + let mut __acc: yeast::Id = #init_body; for #elem_param in __iter { - let #acc_param: usize = __acc; + let #acc_param: yeast::Id = __acc; __acc = #fold_body; } Some(__acc) @@ -616,7 +616,7 @@ fn parse_direct_list(tokens: &mut Tokens, ctx: &Ident) -> Result::into) + { #expr }.into_iter().map(::std::convert::Into::::into) } } else { let expr = group.stream(); @@ -629,7 +629,7 @@ fn parse_direct_list(tokens: &mut Tokens, ctx: &Ident) -> Result::into({ #expr })); + __nodes.push(::std::convert::Into::::into({ #expr })); }); } continue; @@ -649,7 +649,7 @@ struct CaptureInfo { name: String, multiplicity: CaptureMultiplicity, /// `true` for `@@name` captures: the auto-translate prefix skips them, - /// so the bound `NodeRef` refers to the raw (input-schema) node. + /// so the bound `Id` refers to the raw (input-schema) node. raw: bool, } @@ -804,22 +804,17 @@ pub fn parse_rule_top(input: TokenStream) -> Result { match cap.multiplicity { CaptureMultiplicity::Repeated => { quote! { - let #name: Vec = __captures.get_all(#name_str) - .into_iter() - .map(yeast::NodeRef) - .collect(); + let #name: Vec = __captures.get_all(#name_str); } } CaptureMultiplicity::Optional => { quote! { - let #name: Option = - __captures.get_opt(#name_str).map(yeast::NodeRef); + let #name: Option = __captures.get_opt(#name_str); } } CaptureMultiplicity::Single => { quote! { - let #name: yeast::NodeRef = - yeast::NodeRef(__captures.get_var(#name_str).unwrap()); + let #name: yeast::Id = __captures.get_var(#name_str).unwrap(); } } } @@ -850,7 +845,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { __fields.insert( __field_id, #name.into_iter() - .map(::std::convert::Into::::into) + .map(::std::convert::Into::::into) .collect(), ); }, @@ -859,14 +854,14 @@ pub fn parse_rule_top(input: TokenStream) -> Result { .unwrap_or_else(|| panic!("field '{}' not found", #name_str)); if let Some(__id) = #name { __fields.entry(__field_id).or_insert_with(Vec::new) - .push(::std::convert::Into::::into(__id)); + .push(::std::convert::Into::::into(__id)); } }, CaptureMultiplicity::Single => quote! { let __field_id = #ctx_ident.ast.field_id_for_name(#name_str) .unwrap_or_else(|| panic!("field '{}' not found", #name_str)); __fields.entry(__field_id).or_insert_with(Vec::new) - .push(::std::convert::Into::::into(#name)); + .push(::std::convert::Into::::into(#name)); }, } }) @@ -898,7 +893,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { } quote! { - let mut __nodes: Vec = Vec::new(); + let mut __nodes: Vec = Vec::new(); #(#transform_items)* __nodes } @@ -919,7 +914,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { __translator.auto_translate_captures(&mut __captures, __ast, __user_ctx, __skip)?; #(#bindings)* let mut #ctx_ident = yeast::build::BuildCtx::with_translator(__ast, &__captures, __fresh, __source_range, __user_ctx, __translator); - let __result: Vec = { #transform_body }; + let __result: Vec = { #transform_body }; Ok(__result) })) } diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index 3c122e7ebf9..dad36bb0edb 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -302,7 +302,7 @@ already conforms to the output schema. For rules that need the raw (input-schema) capture — typically to read its source text or to translate it explicitly with mutable context state between calls — use `@@name` instead. The body sees the original -input-schema `NodeRef`: +input-schema `Id`: ```rust yeast::rule!( diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index c7c60530599..7875942f9c1 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -176,9 +176,6 @@ impl BuildCtx<'_, C> { /// (translation is not meaningful when input and output share a /// schema). /// - /// Accepts any value convertible to [`Id`] (including [`crate::NodeRef`]), - /// so manual rules can pass capture bindings directly without unwrapping. - /// /// Errors if this `BuildCtx` was constructed by hand (without a /// translator handle) — for example, in unit tests that don't go /// through the rule driver. @@ -191,7 +188,7 @@ impl BuildCtx<'_, C> { } /// Translate an optional capture, returning the first translated id or - /// `None`. Convenience for `?`-quantifier captures (`Option`). + /// `None`. Convenience for `?`-quantifier captures (`Option`). /// /// If the underlying translation produces multiple ids for a single /// input, only the first is returned. For most use cases (e.g. diff --git a/shared/yeast/src/dump.rs b/shared/yeast/src/dump.rs index be496d40bd5..34b61432360 100644 --- a/shared/yeast/src/dump.rs +++ b/shared/yeast/src/dump.rs @@ -1,6 +1,6 @@ use std::fmt::Write; -use crate::{schema::Schema, Ast, Node, NodeContent, CHILD_FIELD}; +use crate::{schema::Schema, Ast, Id, Node, NodeContent, CHILD_FIELD}; /// Options for controlling AST dump output. pub struct DumpOptions { @@ -34,16 +34,11 @@ impl Default for DumpOptions { /// method: /// identifier "foo" /// ``` -pub fn dump_ast(ast: &Ast, root: usize, source: &str) -> String { +pub fn dump_ast(ast: &Ast, root: Id, source: &str) -> String { dump_ast_with_options(ast, root, source, &DumpOptions::default()) } -pub fn dump_ast_with_options( - ast: &Ast, - root: usize, - source: &str, - options: &DumpOptions, -) -> String { +pub fn dump_ast_with_options(ast: &Ast, root: Id, source: &str, options: &DumpOptions) -> String { let mut out = String::new(); dump_node(ast, root, source, options, 0, None, &mut out); out @@ -53,7 +48,7 @@ pub fn dump_ast_with_options( /// /// Any node that does not match the expected type set for its parent field is /// rendered with a trailing `" <-- ERROR: ..."` annotation on the same line. -pub fn dump_ast_with_type_errors(ast: &Ast, root: usize, source: &str, schema: &Schema) -> String { +pub fn dump_ast_with_type_errors(ast: &Ast, root: Id, source: &str, schema: &Schema) -> String { dump_ast_with_type_errors_and_options(ast, root, source, schema, &DumpOptions::default()) } @@ -63,7 +58,7 @@ pub fn dump_ast_with_type_errors(ast: &Ast, root: usize, source: &str, schema: & /// rendered with a trailing `" <-- ERROR: ..."` annotation on the same line. pub fn dump_ast_with_type_errors_and_options( ast: &Ast, - root: usize, + root: Id, source: &str, schema: &Schema, options: &DumpOptions, @@ -176,7 +171,7 @@ fn expected_for_field<'a>( fn dump_node( ast: &Ast, - id: usize, + id: Id, source: &str, options: &DumpOptions, indent: usize, @@ -315,7 +310,7 @@ fn dump_node( /// Dump a leaf node inline (no newline prefix, caller provides context). fn dump_node_inline( ast: &Ast, - id: usize, + id: Id, source: &str, options: &DumpOptions, type_check: Option<( diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 004a8408cb6..bfcaface53a 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -22,37 +22,31 @@ use captures::Captures; pub use cursor::Cursor; use query::QueryNode; -/// Node ids are indexes into the arena -pub type Id = usize; +/// Node id: an index into the [`Ast`] arena. A newtype around `usize` +/// rather than a bare alias so that it can carry its own +/// [`YeastDisplay`] / [`YeastSourceRange`] / [`IntoFieldIds`] impls +/// without colliding with the impls for plain integers. +/// +/// Use `id.0` (or `id.into()`) to obtain the raw arena index. +#[repr(transparent)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize)] +pub struct Id(pub usize); -/// Field and Kind ids are provided by tree-sitter -type FieldId = u16; -type KindId = u16; - -/// A typed reference to a node in an [`Ast`] arena. Wraps an [`Id`] but -/// deliberately does not implement [`std::fmt::Display`]: rendering a node -/// requires the [`Ast`] it lives in (to resolve [`NodeContent::Range`] back -/// to source text). Use [`YeastDisplay::yeast_to_string`] to format it. -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] -pub struct NodeRef(pub Id); - -impl NodeRef { - pub fn id(self) -> Id { - self.0 +impl From for Id { + fn from(value: usize) -> Self { + Id(value) } } -impl From for Id { - fn from(value: NodeRef) -> Self { +impl From for usize { + fn from(value: Id) -> Self { value.0 } } -impl From for NodeRef { - fn from(value: Id) -> Self { - NodeRef(value) - } -} +/// Field and Kind ids are provided by tree-sitter +type FieldId = u16; +type KindId = u16; /// Like [`std::fmt::Display`], but the formatting routine is given access to /// the [`Ast`] so that node references can resolve to their source text. @@ -67,21 +61,21 @@ pub trait YeastDisplay { /// Optional source range for values used in `#{expr}` interpolations. /// /// By default this returns `None`, so synthesized leaves inherit the matched -/// rule's source range. `NodeRef` returns the referenced node's range, letting +/// rule's source range. `Id` returns the referenced node's range, letting /// `(kind #{capture})` carry the captured node's location. pub trait YeastSourceRange { fn yeast_source_range(&self, ast: &Ast) -> Option; } -impl YeastDisplay for NodeRef { +impl YeastDisplay for Id { fn yeast_to_string(&self, ast: &Ast) -> String { - ast.source_text(self.0) + ast.source_text(*self) } } -impl YeastSourceRange for NodeRef { +impl YeastSourceRange for Id { fn yeast_source_range(&self, ast: &Ast) -> Option { - ast.get_node(self.0).and_then(|n| match &n.content { + ast.get_node(*self).and_then(|n| match &n.content { NodeContent::Range(r) => Some(r.clone()), _ => n.source_range, }) @@ -172,7 +166,7 @@ impl<'a> AstCursor<'a> { } impl<'a> Cursor<'a, Ast, Node, FieldId> for AstCursor<'a> { fn node(&self) -> &'a Node { - &self.ast.nodes[self.node_id] + &self.ast.nodes[self.node_id.0] } fn field_id(&self) -> Option { @@ -347,16 +341,16 @@ impl Ast { /// /// This reflects the effective AST after desugaring and excludes orphaned /// arena nodes left behind by rewrite operations. - pub fn reachable_node_ids(&self) -> Vec { + pub fn reachable_node_ids(&self) -> Vec { let mut reachable = Vec::new(); let mut stack = vec![self.root]; let mut seen = vec![false; self.nodes.len()]; while let Some(id) = stack.pop() { - if id >= self.nodes.len() || seen[id] { + if id.0 >= self.nodes.len() || seen[id.0] { continue; } - seen[id] = true; + seen[id.0] = true; reachable.push(id); if let Some(node) = self.get_node(id) { @@ -380,11 +374,11 @@ impl Ast { } pub fn get_node(&self, id: Id) -> Option<&Node> { - self.nodes.get(id) + self.nodes.get(id.0) } pub fn print(&self, source: &str, root_id: Id) -> Value { - let root = &self.nodes()[root_id]; + let root = &self.nodes()[root_id.0]; self.print_node(root, source) } @@ -427,7 +421,7 @@ impl Ast { is_named, source_range, }); - id + Id(id) } fn union_source_range_of_children( @@ -498,7 +492,7 @@ impl Ast { pub fn prepend_field_child(&mut self, node_id: Id, field_id: FieldId, value_id: Id) { let node = self .nodes - .get_mut(node_id) + .get_mut(node_id.0) .expect("prepend_field_child: invalid node id"); node.fields.entry(field_id).or_default().insert(0, value_id); } @@ -524,7 +518,7 @@ impl Ast { fields: BTreeMap::new(), content: NodeContent::DynamicString(content), }); - id + Id(id) } pub fn field_name_for_id(&self, id: FieldId) -> Option<&'static str> { @@ -1008,7 +1002,7 @@ fn apply_repeating_rules_inner( // // Child traversal does not increment rewrite depth and starts fresh // (no rule is skipped on child subtrees). - let mut fields = std::mem::take(&mut ast.nodes[id].fields); + let mut fields = std::mem::take(&mut ast.nodes[id.0].fields); for children in fields.values_mut() { let mut new_children: Option> = None; for (i, &child_id) in children.iter().enumerate() { @@ -1041,7 +1035,7 @@ fn apply_repeating_rules_inner( *children = new; } } - ast.nodes[id].fields = fields; + ast.nodes[id.0].fields = fields; Ok(vec![id]) } diff --git a/shared/yeast/src/visitor.rs b/shared/yeast/src/visitor.rs index 4bd2606c958..bbf4308f133 100644 --- a/shared/yeast/src/visitor.rs +++ b/shared/yeast/src/visitor.rs @@ -49,7 +49,7 @@ impl Visitor { pub fn build_with_schema(self, schema: crate::schema::Schema) -> Ast { Ast { - root: 0, + root: Id(0), schema, nodes: self.nodes.into_iter().map(|n| n.inner).collect(), source: Vec::new(), @@ -72,7 +72,7 @@ impl Visitor { }, parent: self.current, }); - id + Id(id) } fn enter_node(&mut self, node: tree_sitter::Node<'_>) -> bool { @@ -83,10 +83,10 @@ impl Visitor { fn leave_node(&mut self, field_name: Option<&'static str>, _node: tree_sitter::Node<'_>) { let node_id = self.current.unwrap(); - let node_parent = self.nodes[node_id].parent; + let node_parent = self.nodes[node_id.0].parent; if let Some(parent_id) = node_parent { - let parent = self.nodes.get_mut(parent_id).unwrap(); + let parent = self.nodes.get_mut(parent_id.0).unwrap(); if let Some(field) = field_name { let field_id = self.language.field_id_for_name(field).unwrap().get(); parent diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 73243a09ab7..0399316b305 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -1059,7 +1059,7 @@ fn test_one_shot_does_not_recurse_into_wrapper_output() { } /// Verify that `@@name` capture markers skip the auto-translate prefix: -/// the body sees the *raw* (input-schema) NodeRef and can read its +/// the body sees the *raw* (input-schema) `Id` and can read its /// source text or call `ctx.translate(...)` explicitly. Compare with /// the bare `@name` form, where the auto-translate prefix runs the /// same translation up front and the body sees the post-translate id. @@ -1081,7 +1081,7 @@ fn test_raw_capture_marker() { (assignment left: (_) @@raw_lhs right: (_) @rhs) => { - let text = ctx.ast.source_text(raw_lhs.into()); + let text = ctx.ast.source_text(raw_lhs); tree!((call method: (identifier #{text.as_str()}) receiver: {rhs})) @@ -1116,7 +1116,7 @@ fn test_raw_capture_marker() { } /// Companion to `test_raw_capture_marker`: confirms that calling -/// `ctx.translate(raw)` on a `@@`-captured NodeRef from the rule body +/// `ctx.translate(raw)` on a `@@`-captured `Id` from the rule body /// produces the correctly-translated output-schema node. With `@`, the /// translation has already happened, so `ctx.translate(...)` inside the /// body would attempt to re-translate an output node (which has no @@ -1235,10 +1235,8 @@ fn test_desugar_for_with_multiple_assignment() { } /// Regression test: `#{capture}` in a template must render the *source text* -/// of the captured node, not its arena `Id`. Previously, captures were bound -/// as `usize`, so `#{cap}` printed the integer id (e.g. `"3"`) via `Display`. -/// Captures are now bound as `NodeRef`, which has no `Display` impl and -/// resolves to the captured node's source text via `YeastDisplay`. +/// of the captured node, not its arena `Id`. Captures are bound as `Id`, +/// whose `YeastDisplay` impl resolves to the captured node's source text. #[test] fn test_hash_brace_renders_capture_source_text() { let rule: Rule = rule!( @@ -1266,7 +1264,7 @@ fn test_hash_brace_renders_capture_source_text() { ); } -/// Regression test: non-`NodeRef` values in `#{expr}` still render via their +/// Regression test: non-`Id` values in `#{expr}` still render via their /// `Display` impl (covered by `YeastDisplay`'s blanket impls for primitives). #[test] fn test_hash_brace_renders_integer_expression() { @@ -1304,7 +1302,7 @@ fn test_hash_brace_uses_capture_location_for_leaf() { let ast = run_and_ast("foo.bar()", vec![rule]); - let mut bar_ids: Vec = Vec::new(); + let mut bar_ids: Vec = Vec::new(); for id in ast.reachable_node_ids() { let Some(node) = ast.get_node(id) else { continue; diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index c5025228cc9..cf78b510ebb 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -15,26 +15,26 @@ struct SwiftContext { /// (`computed_getter`/`computed_setter`/`computed_modify`/ /// `willset_clause`/`didset_clause`/`getter_specifier`/ /// `setter_specifier`). - property_name: Option, + property_name: Option, /// Translated type node for the property type. Set by the outer /// `property_binding` rule (computed accessors variant) and /// `protocol_property_declaration` when present; read by the /// accessor inner rules. - property_type: Option, + property_type: Option, /// Default-value expression for the next translated `parameter`. Set /// by the outer `function_parameter` rule; read by the `parameter` /// rules. - default_value: Option, + default_value: Option, /// Translated outer modifiers (e.g. visibility, attributes) to /// attach to each child of a flattening outer rule. Set by /// `property_declaration`, `enum_entry`, and /// `protocol_property_declaration`. - outer_modifiers: Vec, + outer_modifiers: Vec, /// The `let`/`var` binding modifier for a `property_declaration`. /// Set by `property_declaration`; read by the inner declaration /// rules (`property_binding` variants, accessor rules) so they /// emit it as part of the output node's `modifier:` field. - binding_modifier: Option, + binding_modifier: Option, /// True when the current child of a flattening outer rule is not /// the first one — its inner rule should emit a /// `chained_declaration` modifier so the original grouping can be @@ -45,10 +45,10 @@ struct SwiftContext { /// Build a freshly-created `chained_declaration` modifier node if /// `ctx.is_chained`, else `None`. Used by inner declaration rules to /// emit the chained tag for non-first children of a flattening outer -/// rule. Returns `Option` so it splices via `{..…}` to 0 or 1 ids. -fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Option { +/// rule. Returns `Option` so it splices via `{..…}` to 0 or 1 ids. +fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Option { if ctx.is_chained { - Some(ctx.literal("modifier", "chained_declaration").into()) + Some(ctx.literal("modifier", "chained_declaration")) } else { None } @@ -63,10 +63,10 @@ fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Optio /// condition. fn and_chain( ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>, - conds: Vec, + conds: Vec, ) -> yeast::Id { - conds.into_iter() - .map(yeast::Id::from) + conds + .into_iter() .reduce(|acc, elem| { tree!((binary_expr operator: (infix_operator "&&") left: {acc} right: {elem})) }) @@ -79,7 +79,7 @@ fn and_chain( /// guarantees at least one part. fn member_chain( ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>, - parts: Vec, + parts: Vec, ) -> yeast::Id { let mut iter = parts.into_iter(); let first = iter @@ -199,7 +199,7 @@ fn translation_rules() -> Vec> { computed_value: (computed_property accessor: _+ @@accessors)) => {..{ - ctx.property_name = Some(tree!((identifier #{pattern})).into()); + ctx.property_name = Some(tree!((identifier #{pattern}))); ctx.property_type = ty; let mut result = Vec::new(); @@ -261,7 +261,7 @@ fn translation_rules() -> Vec> { ); // Publish the property name for the observer rules. - ctx.property_name = Some(tree!((identifier #{name})).into()); + ctx.property_name = Some(tree!((identifier #{name}))); // Observers are subsequent outputs of this flattening // rule, so they always get `chained_declaration`. ctx.is_chained = true; @@ -306,8 +306,8 @@ fn translation_rules() -> Vec> { (modifiers)* @mods) => {..{ - let binding_text = ctx.ast.source_text(binding_kind.into()); - ctx.binding_modifier = Some(ctx.literal("modifier", &binding_text).into()); + let binding_text = ctx.ast.source_text(binding_kind); + ctx.binding_modifier = Some(ctx.literal("modifier", &binding_text)); ctx.outer_modifiers = mods; let mut result = Vec::new(); @@ -714,7 +714,7 @@ fn translation_rules() -> Vec> { ), // Labeled statement (e.g. `outer: for ...`). Strip the trailing ':' from the label token. rule!((labeled_statement label: (statement_label) @lbl statement: @stmt) => { - let text = ctx.ast.source_text(lbl.into()); + let text = ctx.ast.source_text(lbl); let name = &text[..text.len() - 1]; tree!((labeled_stmt label: (identifier #{name}) stmt: {stmt})) }), @@ -1010,7 +1010,7 @@ fn translation_rules() -> Vec> { (modifiers)* @mods) => {..{ - ctx.property_name = Some(tree!((identifier #{name})).into()); + ctx.property_name = Some(tree!((identifier #{name}))); ctx.property_type = ty; ctx.outer_modifiers = mods;