Uh oh!
There was an error while loading. Please reload this page.
Rust: Macro call resolution - #20548
Conversation
fd9f2fb to
ab4e8bcCompareUh oh!
There was an error while loading. Please reload this page.
d43b6d2 to
89b33abCompare89b33ab to
701cff3CompareThere was a problem hiding this comment.
Pull Request Overview
This PR implements resolution of macro calls in Rust, providing better accuracy for $crate path resolution and reducing type inference inconsistencies. The changes introduce macro namespace support and improve path resolution across the codebase.
Key Changes:
- Added macro namespace support alongside existing value and type namespaces
- Implemented macro call resolution with new
MacroItemNodeclasses - Enhanced
$cratepath resolution within macro expansions - Updated path resolution logic to handle macro-specific visibility and scoping rules
Reviewed Changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| PathResolution.qll | Core implementation of macro namespace, macro item nodes, and enhanced path resolution logic |
| PathResolutionConsistency.qll | Updated consistency checks to handle macro resolution improvements |
| MacroCallImpl.qll | Added macro resolution capability to MacroCall AST nodes |
| PathResolutionInlineExpectationsTest.qll | Enhanced test filtering to exclude macro-specific test markers |
| Various .expected files | Updated test expectations reflecting improved macro resolution accuracy |
| Various test source files | Added macro resolution test annotations and examples |
| abstract Attr getAnAttr(); | ||
| pragma[nomagic] | ||
| final Attr getAttr(string name) { | ||
| result = this.getAnAttr() and | ||
| result.getMeta().getPath().(RelevantPath).isUnqualified(name) | ||
| } | ||
There was a problem hiding this comment.
The new attribute-related methods (getAnAttr, getAttr, hasAttr) lack documentation explaining their purpose and usage. These methods appear to be fundamental for macro resolution but need proper documentation.
| abstractAttrgetAnAttr(); | |
| pragma[nomagic] | |
| finalAttrgetAttr(stringname){ | |
| result=this.getAnAttr()and | |
| result.getMeta().getPath().(RelevantPath).isUnqualified(name) | |
| } | |
| /** | |
| *Getsan attribute attached to this item. | |
| * | |
| *Returnsan attribute (`Attr`)that is associated with this item,ifany. | |
| *Thismethod may return any one of the attributes present; to retrieve a specific | |
| *attributebyname,use `getAttr`. | |
| */ | |
| abstractAttrgetAnAttr(); | |
| /** | |
| *Getsan attribute with the given name attached to this item. | |
| * | |
| * @param nameTheunqualified name of the attribute to retrieve. | |
| * @return Anattribute(`Attr`)with the specified name,if present on this item. | |
| *Ifmultiple attributes with the same name are present, returns one of them. | |
| */ | |
| pragma[nomagic] | |
| finalAttrgetAttr(stringname){ | |
| result=this.getAnAttr()and | |
| result.getMeta().getPath().(RelevantPath).isUnqualified(name) | |
| } | |
| /** | |
| *Holdsif this item has an attribute with the given name. | |
| * | |
| * @param nameTheunqualified name of the attribute to check for. | |
| * @return Trueif this item has at least one attribute with the specified name. | |
| */ |
| override Namespace getNamespace() { | ||
| // see https://doc.rust-lang.org/reference/procedural-macros.html | ||
| if this.hasAttr(["proc_macro", "proc_macro_attribute", "proc_macro_derive"]) | ||
| then result.isMacro() | ||
| else result.isValue() | ||
| } |
There was a problem hiding this comment.
The logic for determining function namespace based on procedural macro attributes needs more detailed explanation. The comment references external documentation but doesn't explain the specific behavior being implemented.
| private predicate macroUseEdge( | ||
| ItemNode i, string name, SuccessorKind kind, UseOption useOpt, MacroItemNode macro | ||
| ) { | ||
| exists(ItemNode m | | ||
| m = i.getASuccessor(_, _, useOpt) and | ||
| m.hasAttr("macro_use") | ||
| | | ||
| macro = m.(ModuleItemNode).getASuccessor(name, kind, _) | ||
| or | ||
| macro = m.(ExternCrateItemNode).getASuccessor(_, _, _).getASuccessor(name, kind, _) | ||
| ) | ||
| } |
There was a problem hiding this comment.
The macroUseEdge predicate implements complex macro visibility rules but lacks documentation explaining the two different resolution paths (ModuleItemNode vs ExternCrateItemNode) and when each applies.
| exists(Path macroDefPath | | ||
| isInMacroExpansion(macroDefPath, n) and | ||
| crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile() |
There was a problem hiding this comment.
The isInMacroFromCrateExpansion predicate has a documented limitation ('may originate from crate') but the implementation logic for determining crate origin is not clearly explained. The file-based comparison seems fragile and needs clarification.
| exists(PathmacroDefPath| | |
| isInMacroExpansion(macroDefPath, n)and | |
| crate.getASourceFile().getFile()=resolvePathCand(macroDefPath).getFile() | |
| exists(PathmacroDefPath,ItemNodemacroDefItem| | |
| isInMacroExpansion(macroDefPath, n)and | |
| macroDefItem=resolvePathCand(macroDefPath)and | |
| macroDefItem.getEnclosingCrate()= crate |
paldepind
left a comment
There was a problem hiding this comment.
Looks good!
If it's not too hard, I think it would be worthwhile adding tests for $crate as the implementation is not trivial, especially when $crate is in an expansion within an expansion.
| } | ||
| pragma[nomagic] | ||
| private predicate isMacroExpansion(AstNode expansion, Path macroDefPath) { |
There was a problem hiding this comment.
The argument order seems inconsistent with isInMacroExpansion below.
| privatepredicateisMacroExpansion(AstNodeexpansion,PathmacroDefPath){ | |
| privatepredicateisMacroExpansion(PathmacroDefPath,AstNodeexpansion){ |
Uh oh!
There was an error while loading. Please reload this page.
| predicate isInMacroFromCrateExpansion(CrateItemNode crate, AstNode n) { | ||
| exists(Path macroDefPath | | ||
| isInMacroExpansion(macroDefPath, n) and | ||
| crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile() |
There was a problem hiding this comment.
This predicate is only used with n being a $crate path. Inlining that as below changes the predicate size from 1.354.637 to 4.683 on the path resolution test DB.
Making it a predicate with result might also be more natural?
| predicateisInMacroFromCrateExpansion(CrateItemNodecrate,AstNoden){ | |
| exists(PathmacroDefPath| | |
| isInMacroExpansion(macroDefPath, n)and | |
| crate.getASourceFile().getFile()=resolvePathCand(macroDefPath).getFile() | |
| CrateItemNodegetDollarCratePathCrate(RelevantPathdollarCratePath){ | |
| dollarCratePath.isDollarCrate()and | |
| exists(PathmacroDefPath| | |
| isInMacroExpansion(macroDefPath, dollarCratePath)and | |
| result.getASourceFile().getFile()=resolvePathCand(macroDefPath).getFile() |
| exists(RelevantPath path | | ||
| path.isUnqualified(name, encl) and | ||
| ancestor = encl and | ||
| exists(ns) and |
There was a problem hiding this comment.
This is not needed and ns is restricted in one of the cases below.
| exists(ns) and |
| | | ||
| pathUsesNamespace(path, ns) | ||
| or | ||
| not pathUsesNamespace(path, _) |
There was a problem hiding this comment.
We could add the exists(ns) here for clarity, but it doesn't change the semantics.
| notpathUsesNamespace(path, _) | |
| notpathUsesNamespace(path, _)andexists(ns) |
| // known limitation for `$crate` | ||
| not p.getQualifier*().(RelevantPath).isUnqualified("$crate") and | ||
| // `panic` is defined in both `std` and `core`; both are included in the prelude | ||
| not p.getText() = "panic" and |
There was a problem hiding this comment.
Is panic the only thing where this the case?
There was a problem hiding this comment.
It's the only thing I saw in the tests, at least.
paldepind
left a comment
There was a problem hiding this comment.
Why do path resolution inconsistencies increase for some projects? For instance, Tokio increases from 655 to 9030.
| } | ||
| pragma[nomagic] | ||
| predicate isInDollarCrateSupportedMacroExpansion(File macroDefFile, AstNode n) { |
There was a problem hiding this comment.
| predicateisInDollarCrateSupportedMacroExpansion(FilemacroDefFile,AstNoden){ | |
| privatepredicateisInDollarCrateSupportedMacroExpansion(FilemacroDefFile,AstNoden){ |
| * calls. | ||
| */ | ||
| pragma[nomagic] | ||
| predicate resolveDollarCrate(RelevantPath p, CrateItemNode crate) { |
There was a problem hiding this comment.
| predicateresolveDollarCrate(RelevantPathp,CrateItemNodecrate){ | |
| privatepredicateresolveDollarCrate(RelevantPathp,CrateItemNodecrate){ |
| macroDefFile = resolvePathCand(macroDefPath).getFile() | ||
| ) | ||
| or | ||
| isInDollarCrateSupportedMacroExpansion(macroDefFile, n.getParentNode()) |
There was a problem hiding this comment.
This predicate holds for all AST nodes within an expansion, but we only care about $crate notes. And for expansions within expansions we end up with 2 * nrOfAstNodes tuples?
Wouldn't it have been more efficient to have
- a predicate from
$cratepaths to their enclosing expansion and - a predicate from a macro call expansion to its enclosing macro call expansion.
And then use these in resolveDollarCrate to find all the potential crates? That would avoid a predicate that holds for all AST nodes within expansions.
There was a problem hiding this comment.
I'll use doublyBoundedFastTC.
There was a problem hiding this comment.
Thanks, after looking up what doublyBoundedFastTC I think that approach is really clean!
hvitved
commented
Oct 2, 2025
This happens because we are now also resolving paths to macros, and in some cases those are not unique. We could investigate follow-up. |
paldepind
commented
Oct 2, 2025
Thanks. That seems reasonable :) |
paldepind
commented
Oct 2, 2025
I've approved, but maybe we need another DCA run? |
Uh oh!
There was an error while loading. Please reload this page.
This PR implements resolution of macro calls (macro expansion is still done in the extractor). The motivation for doing this, in addition to having the resolution information available, is to provide more accurate resolution of
$cratepaths, which in turn means fewer inconsistencies.DCA looks great; a significant reduction in type inference inconsistencies (down
20,267from83,257) and a small performance improvement, at the cost of only a small reduction in resolved calls.