Skip to content

Rust: expand attribute macros on AssocItem and ExternItem #19823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions rust/extractor/src/translate/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,38 @@ 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)*) => {};
}

// 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<crate::trap::Label<generated::AssocItem>> for crate::trap::Label<generated::Item> {
fn from(value: crate::trap::Label<generated::AssocItem>) -> Self {
// 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<crate::trap::Label<generated::ExternItem>> for crate::trap::Label<generated::Item> {
fn from(value: crate::trap::Label<generated::ExternItem>) -> Self {
// SAFETY: this is safe because every concrete instance of `@extern_item` is also an instance of `@item`
unsafe { Self::from_untyped(value.as_untyped()) }
}
}
#[macro_export]
macro_rules! post_emit {
(MacroCall, $self:ident, $node:ident, $label:ident) => {
Expand Down Expand Up @@ -62,6 +88,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::<Label<generated::AssocItem>>::from($label),
);
};
(ExternItem, $self:ident, $node:ident, $label:ident) => {
$self.emit_item_expansion(
&$node.clone().into(),
From::<Label<generated::ExternItem>>::from($label),
);
};
// TODO canonical origin of other items
(PathExpr, $self:ident, $node:ident, $label:ident) => {
$self.extract_path_canonical_destination($node, $label.into());
Expand Down Expand Up @@ -694,10 +732,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<Label<generated::Item>> {
) -> Option<Label<generated::MacroCall>> {
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) {
Expand All @@ -714,13 +763,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
Expand All @@ -730,10 +776,7 @@ impl<'a> Translator<'a> {
&mut self,
node: &ast::Item,
) -> Option<Label<generated::MacroItems>> {
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;
Expand All @@ -743,7 +786,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();
Expand Down
180 changes: 145 additions & 35 deletions rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading