From b7f66f865307f56d2ada185dcc3f16e48a9adde5 Mon Sep 17 00:00:00 2001 From: Arthur Baars Date: Thu, 19 Jun 2025 16:57:27 +0200 Subject: [PATCH 1/2] Rust: Rust: expand attribute macros on `AssocItem` and `ExternItem` --- rust/extractor/src/translate/base.rs | 64 +++++-- .../macro-expansion/PrintAst.expected | 180 ++++++++++++++---- .../macro-expansion/test.expected | 16 +- 3 files changed, 209 insertions(+), 51 deletions(-) diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 9e9123c45709..3a34bf29fa5a 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -26,12 +26,33 @@ use ra_ap_syntax::{ macro_rules! pre_emit { (Item, $self:ident, $node:ident) => { if let Some(label) = $self.prepare_item_expansion($node) { - return Some(label); + return Some(label.into()); + } + }; + (AssocItem, $self:ident, $node:ident) => { + if let Some(label) = $self.prepare_item_expansion(&$node.clone().into()) { + return Some(label.into()); + } + }; + (ExternItem, $self:ident, $node:ident) => { + if let Some(label) = $self.prepare_item_expansion(&$node.clone().into()) { + return Some(label.into()); } }; ($($_:tt)*) => {}; } - +impl From> for crate::trap::Label { + fn from(value: crate::trap::Label) -> Self { + // SAFETY: this is safe because in the dbscheme Item is a subclass of Stmt + unsafe { Self::from_untyped(value.as_untyped()) } + } +} +impl From> for crate::trap::Label { + fn from(value: crate::trap::Label) -> Self { + // SAFETY: this is safe because in the dbscheme Item is a subclass of Stmt + unsafe { Self::from_untyped(value.as_untyped()) } + } +} #[macro_export] macro_rules! post_emit { (MacroCall, $self:ident, $node:ident, $label:ident) => { @@ -62,6 +83,18 @@ macro_rules! post_emit { (Item, $self:ident, $node:ident, $label:ident) => { $self.emit_item_expansion($node, $label); }; + (AssocItem, $self:ident, $node:ident, $label:ident) => { + $self.emit_item_expansion( + &$node.clone().into(), + From::>::from($label), + ); + }; + (ExternItem, $self:ident, $node:ident, $label:ident) => { + $self.emit_item_expansion( + &$node.clone().into(), + From::>::from($label), + ); + }; // TODO canonical origin of other items (PathExpr, $self:ident, $node:ident, $label:ident) => { $self.extract_path_canonical_destination($node, $label.into()); @@ -694,10 +727,21 @@ impl<'a> Translator<'a> { } } + fn is_attribute_macro_target(&self, node: &ast::Item) -> bool { + // rust-analyzer considers as an `attr_macro_call` also a plain macro call, but we want to + // process that differently (in `extract_macro_call_expanded`) + !matches!(node, ast::Item::MacroCall(_)) + && self.semantics.is_some_and(|semantics| { + let file = semantics.hir_file_for(node.syntax()); + let node = InFile::new(file, node); + semantics.is_attr_macro_call(node) + }) + } + pub(crate) fn prepare_item_expansion( &mut self, node: &ast::Item, - ) -> Option> { + ) -> Option> { if self.source_kind == SourceKind::Library { // if the item expands via an attribute macro, we want to only emit the expansion if let Some(expanded) = self.emit_attribute_macro_expansion(node) { @@ -714,13 +758,10 @@ impl<'a> Translator<'a> { expanded.into(), &mut self.trap.writer, ); - return Some(label.into()); + return Some(label); } } - let semantics = self.semantics.as_ref()?; - let file = semantics.hir_file_for(node.syntax()); - let node = InFile::new(file, node); - if semantics.is_attr_macro_call(node) { + if self.is_attribute_macro_target(node) { self.macro_context_depth += 1; } None @@ -730,10 +771,7 @@ impl<'a> Translator<'a> { &mut self, node: &ast::Item, ) -> Option> { - let semantics = self.semantics?; - let file = semantics.hir_file_for(node.syntax()); - let infile_node = InFile::new(file, node); - if !semantics.is_attr_macro_call(infile_node) { + if !self.is_attribute_macro_target(node) { return None; } self.macro_context_depth -= 1; @@ -743,7 +781,7 @@ impl<'a> Translator<'a> { } let ExpandResult { value: expanded, .. - } = semantics.expand_attr_macro(node)?; + } = self.semantics.and_then(|s| s.expand_attr_macro(node))?; self.emit_macro_expansion_parse_errors(node, &expanded); let macro_items = ast::MacroItems::cast(expanded).or_else(|| { let message = "attribute macro expansion cannot be cast to MacroItems".to_owned(); diff --git a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected index 092c26051d65..a89072531ccc 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected @@ -406,6 +406,151 @@ macro_expansion.rs: # 30| getItem(6): [Impl] impl S { ... } # 30| getAssocItemList(): [AssocItemList] AssocItemList # 31| getAssocItem(0): [Function] fn bzz +# 32| getAttributeMacroExpansion(): [MacroItems] MacroItems +# 32| getItem(0): [Function] fn bzz_0 +# 32| getParamList(): [ParamList] ParamList +# 32| getBody(): [BlockExpr] { ... } +# 32| getStmtList(): [StmtList] StmtList +# 33| getStatement(0): [ExprStmt] ExprStmt +# 33| getExpr(): [MacroExpr] MacroExpr +# 33| getMacroCall(): [MacroCall] hello!... +# 33| getPath(): [Path] hello +# 33| getSegment(): [PathSegment] hello +# 33| getIdentifier(): [NameRef] hello +# 33| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] println!... +# 31| getPath(): [Path] println +# 31| getSegment(): [PathSegment] println +# 31| getIdentifier(): [NameRef] println +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getTailExpr(): [BlockExpr] { ... } +# 31| getStmtList(): [StmtList] StmtList +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [CallExpr] ...::_print(...) +# 31| getArgList(): [ArgList] ArgList +# 31| getArg(0): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] ...::format_args_nl!... +# 31| getPath(): [Path] ...::format_args_nl +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] format_args_nl +# 31| getIdentifier(): [NameRef] format_args_nl +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr +# 31| getTemplate(): [StringLiteralExpr] "hello!\n" +# 31| getFunction(): [PathExpr] ...::_print +# 31| getPath(): [Path] ...::_print +# 31| getQualifier(): [Path] ...::io +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] io +# 31| getIdentifier(): [NameRef] io +# 31| getSegment(): [PathSegment] _print +# 31| getIdentifier(): [NameRef] _print +# 32| getName(): [Name] bzz_0 +# 32| getVisibility(): [Visibility] Visibility +# 32| getItem(1): [Function] fn bzz_1 +# 32| getParamList(): [ParamList] ParamList +# 32| getBody(): [BlockExpr] { ... } +# 32| getStmtList(): [StmtList] StmtList +# 33| getStatement(0): [ExprStmt] ExprStmt +# 33| getExpr(): [MacroExpr] MacroExpr +# 33| getMacroCall(): [MacroCall] hello!... +# 33| getPath(): [Path] hello +# 33| getSegment(): [PathSegment] hello +# 33| getIdentifier(): [NameRef] hello +# 33| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] println!... +# 31| getPath(): [Path] println +# 31| getSegment(): [PathSegment] println +# 31| getIdentifier(): [NameRef] println +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getTailExpr(): [BlockExpr] { ... } +# 31| getStmtList(): [StmtList] StmtList +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [CallExpr] ...::_print(...) +# 31| getArgList(): [ArgList] ArgList +# 31| getArg(0): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] ...::format_args_nl!... +# 31| getPath(): [Path] ...::format_args_nl +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] format_args_nl +# 31| getIdentifier(): [NameRef] format_args_nl +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr +# 31| getTemplate(): [StringLiteralExpr] "hello!\n" +# 31| getFunction(): [PathExpr] ...::_print +# 31| getPath(): [Path] ...::_print +# 31| getQualifier(): [Path] ...::io +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] io +# 31| getIdentifier(): [NameRef] io +# 31| getSegment(): [PathSegment] _print +# 31| getIdentifier(): [NameRef] _print +# 32| getName(): [Name] bzz_1 +# 32| getVisibility(): [Visibility] Visibility +# 32| getItem(2): [Function] fn bzz_2 +# 32| getParamList(): [ParamList] ParamList +# 32| getBody(): [BlockExpr] { ... } +# 32| getStmtList(): [StmtList] StmtList +# 33| getStatement(0): [ExprStmt] ExprStmt +# 33| getExpr(): [MacroExpr] MacroExpr +# 33| getMacroCall(): [MacroCall] hello!... +# 33| getPath(): [Path] hello +# 33| getSegment(): [PathSegment] hello +# 33| getIdentifier(): [NameRef] hello +# 33| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] println!... +# 31| getPath(): [Path] println +# 31| getSegment(): [PathSegment] println +# 31| getIdentifier(): [NameRef] println +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr +# 31| getTailExpr(): [BlockExpr] { ... } +# 31| getStmtList(): [StmtList] StmtList +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [CallExpr] ...::_print(...) +# 31| getArgList(): [ArgList] ArgList +# 31| getArg(0): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] ...::format_args_nl!... +# 31| getPath(): [Path] ...::format_args_nl +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] format_args_nl +# 31| getIdentifier(): [NameRef] format_args_nl +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr +# 31| getTemplate(): [StringLiteralExpr] "hello!\n" +# 31| getFunction(): [PathExpr] ...::_print +# 31| getPath(): [Path] ...::_print +# 31| getQualifier(): [Path] ...::io +# 31| getQualifier(): [Path] $crate +# 31| getSegment(): [PathSegment] $crate +# 31| getIdentifier(): [NameRef] $crate +# 31| getSegment(): [PathSegment] io +# 31| getIdentifier(): [NameRef] io +# 31| getSegment(): [PathSegment] _print +# 31| getIdentifier(): [NameRef] _print +# 32| getName(): [Name] bzz_2 +# 32| getVisibility(): [Visibility] Visibility # 32| getParamList(): [ParamList] ParamList # 31| getAttr(0): [Attr] Attr # 31| getMeta(): [Meta] Meta @@ -422,41 +567,6 @@ macro_expansion.rs: # 33| getSegment(): [PathSegment] hello # 33| getIdentifier(): [NameRef] hello # 33| getTokenTree(): [TokenTree] TokenTree -# 33| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr -# 33| getStatement(0): [ExprStmt] ExprStmt -# 33| getExpr(): [MacroExpr] MacroExpr -# 33| getMacroCall(): [MacroCall] println!... -# 33| getPath(): [Path] println -# 33| getSegment(): [PathSegment] println -# 33| getIdentifier(): [NameRef] println -# 33| getTokenTree(): [TokenTree] TokenTree -# 33| getMacroCallExpansion(): [MacroBlockExpr] MacroBlockExpr -# 33| getTailExpr(): [BlockExpr] { ... } -# 33| getStmtList(): [StmtList] StmtList -# 33| getStatement(0): [ExprStmt] ExprStmt -# 33| getExpr(): [CallExpr] ...::_print(...) -# 33| getArgList(): [ArgList] ArgList -# 33| getArg(0): [MacroExpr] MacroExpr -# 33| getMacroCall(): [MacroCall] ...::format_args_nl!... -# 33| getPath(): [Path] ...::format_args_nl -# 33| getQualifier(): [Path] $crate -# 33| getSegment(): [PathSegment] $crate -# 33| getIdentifier(): [NameRef] $crate -# 33| getSegment(): [PathSegment] format_args_nl -# 33| getIdentifier(): [NameRef] format_args_nl -# 33| getTokenTree(): [TokenTree] TokenTree -# 33| getMacroCallExpansion(): [FormatArgsExpr] FormatArgsExpr -# 33| getTemplate(): [StringLiteralExpr] "hello!\n" -# 33| getFunction(): [PathExpr] ...::_print -# 33| getPath(): [Path] ...::_print -# 33| getQualifier(): [Path] ...::io -# 33| getQualifier(): [Path] $crate -# 33| getSegment(): [PathSegment] $crate -# 33| getIdentifier(): [NameRef] $crate -# 33| getSegment(): [PathSegment] io -# 33| getIdentifier(): [NameRef] io -# 33| getSegment(): [PathSegment] _print -# 33| getIdentifier(): [NameRef] _print # 32| getName(): [Name] bzz # 32| getVisibility(): [Visibility] Visibility # 30| getSelfTy(): [PathTypeRepr] S diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index 10a73fd947c8..5940c63da2b4 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -11,13 +11,22 @@ attribute_macros | macro_expansion.rs:15:1:16:14 | fn bar_0 | 1 | macro_expansion.rs:16:1:16:14 | fn bar_0_new | | macro_expansion.rs:15:1:16:14 | fn bar_1 | 0 | macro_expansion.rs:16:1:16:14 | fn bar_1 | | macro_expansion.rs:15:1:16:14 | fn bar_1 | 1 | macro_expansion.rs:16:1:16:14 | fn bar_1_new | +| macro_expansion.rs:31:5:34:5 | fn bzz | 0 | macro_expansion.rs:32:5:33:17 | fn bzz_0 | +| macro_expansion.rs:31:5:34:5 | fn bzz | 1 | macro_expansion.rs:32:5:33:17 | fn bzz_1 | +| macro_expansion.rs:31:5:34:5 | fn bzz | 2 | macro_expansion.rs:32:5:33:17 | fn bzz_2 | macro_calls | included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | | macro_expansion.rs:5:9:5:34 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:34 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | -| macro_expansion.rs:33:9:33:16 | ...::format_args_nl!... | macro_expansion.rs:33:9:33:16 | FormatArgsExpr | -| macro_expansion.rs:33:9:33:16 | hello!... | macro_expansion.rs:33:9:33:16 | MacroBlockExpr | -| macro_expansion.rs:33:9:33:16 | println!... | macro_expansion.rs:33:9:33:16 | MacroBlockExpr | +| macro_expansion.rs:31:5:31:16 | ...::format_args_nl!... | macro_expansion.rs:31:5:31:16 | FormatArgsExpr | +| macro_expansion.rs:31:5:31:16 | ...::format_args_nl!... | macro_expansion.rs:31:5:31:16 | FormatArgsExpr | +| macro_expansion.rs:31:5:31:16 | ...::format_args_nl!... | macro_expansion.rs:31:5:31:16 | FormatArgsExpr | +| macro_expansion.rs:31:5:31:16 | println!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | +| macro_expansion.rs:31:5:31:16 | println!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | +| macro_expansion.rs:31:5:31:16 | println!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | +| macro_expansion.rs:33:9:33:15 | hello!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | +| macro_expansion.rs:33:9:33:15 | hello!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | +| macro_expansion.rs:33:9:33:15 | hello!... | macro_expansion.rs:31:5:31:16 | MacroBlockExpr | | macro_expansion.rs:44:5:44:13 | def_x!... | macro_expansion.rs:44:5:44:13 | MacroItems | | macro_expansion.rs:53:9:53:25 | concat!... | macro_expansion.rs:53:17:53:24 | "xy" | | macro_expansion.rs:55:9:58:5 | my_macro!... | macro_expansion.rs:56:9:57:13 | MacroExpr | @@ -34,4 +43,5 @@ macro_calls | macro_expansion.rs:79:12:79:20 | my_int!... | macro_expansion.rs:79:12:79:20 | i32 | unexpanded_macro_calls | macro_expansion.rs:5:9:5:35 | concat!... | +| macro_expansion.rs:33:9:33:16 | hello!... | warnings From 2acce96bb5ed7c4168ae9072c36b2676cab0640a Mon Sep 17 00:00:00 2001 From: Arthur Baars Date: Fri, 20 Jun 2025 13:20:35 +0200 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Paolo Tranquilli --- rust/extractor/src/translate/base.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 3a34bf29fa5a..c8ebf1309bf2 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -41,15 +41,20 @@ macro_rules! pre_emit { }; ($($_:tt)*) => {}; } + +// TODO: remove the mannually written Label conversions. These can be auto-generated by +// changing the base class of AssocItem from AstNode to Item impl From> for crate::trap::Label { fn from(value: crate::trap::Label) -> Self { - // SAFETY: this is safe because in the dbscheme Item is a subclass of Stmt + // SAFETY: this is safe because every concrete instance of `@assoc_item` is also an instance of `@item` unsafe { Self::from_untyped(value.as_untyped()) } } } +// TODO: remove the mannually written Label conversions. These can be auto-generated by +// changing the base class of ExternItem from AstNode to Item impl From> for crate::trap::Label { fn from(value: crate::trap::Label) -> Self { - // SAFETY: this is safe because in the dbscheme Item is a subclass of Stmt + // SAFETY: this is safe because every concrete instance of `@extern_item` is also an instance of `@item` unsafe { Self::from_untyped(value.as_untyped()) } } }