-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Rust: Macro call resolution #20548
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
base: main
Are you sure you want to change the base?
Rust: Macro call resolution #20548
Conversation
fd9f2fb
to
ab4e8bc
Compare
d43b6d2
to
89b33ab
Compare
89b33ab
to
701cff3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
MacroItemNode
classes - Enhanced
$crate
path 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
abstract Attr getAnAttr(); | |
pragma[nomagic] | |
final Attr getAttr(string name) { | |
result = this.getAnAttr() and | |
result.getMeta().getPath().(RelevantPath).isUnqualified(name) | |
} | |
/** | |
* Gets an attribute attached to this item. | |
* | |
* Returns an attribute (`Attr`) that is associated with this item, if any. | |
* This method may return any one of the attributes present; to retrieve a specific | |
* attribute by name, use `getAttr`. | |
*/ | |
abstract Attr getAnAttr(); | |
/** | |
* Gets an attribute with the given name attached to this item. | |
* | |
* @param name The unqualified name of the attribute to retrieve. | |
* @return An attribute (`Attr`) with the specified name, if present on this item. | |
* If multiple attributes with the same name are present, returns one of them. | |
*/ | |
pragma[nomagic] | |
final Attr getAttr(string name) { | |
result = this.getAnAttr() and | |
result.getMeta().getPath().(RelevantPath).isUnqualified(name) | |
} | |
/** | |
* Holds if this item has an attribute with the given name. | |
* | |
* @param name The unqualified name of the attribute to check for. | |
* @return True if this item has at least one attribute with the specified name. | |
*/ |
Copilot uses AI. Check for mistakes.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Copilot uses AI. Check for mistakes.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macroUseEdge predicate implements complex macro visibility rules but lacks documentation explaining the two different resolution paths (ModuleItemNode vs ExternCrateItemNode) and when each applies.
Copilot uses AI. Check for mistakes.
exists(Path macroDefPath | | ||
isInMacroExpansion(macroDefPath, n) and | ||
crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(Path macroDefPath | | |
isInMacroExpansion(macroDefPath, n) and | |
crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile() | |
exists(Path macroDefPath, ItemNode macroDefItem | | |
isInMacroExpansion(macroDefPath, n) and | |
macroDefItem = resolvePathCand(macroDefPath) and | |
macroDefItem.getEnclosingCrate() = crate |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument order seems inconsistent with isInMacroExpansion
below.
private predicate isMacroExpansion(AstNode expansion, Path macroDefPath) { | |
private predicate isMacroExpansion(Path macroDefPath, AstNode expansion) { |
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
predicate isInMacroFromCrateExpansion(CrateItemNode crate, AstNode n) { | |
exists(Path macroDefPath | | |
isInMacroExpansion(macroDefPath, n) and | |
crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile() | |
CrateItemNode getDollarCratePathCrate(RelevantPath dollarCratePath) { | |
dollarCratePath.isDollarCrate() and | |
exists(Path macroDefPath | | |
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add the exists(ns)
here for clarity, but it doesn't change the semantics.
not pathUsesNamespace(path, _) | |
not pathUsesNamespace(path, _) and exists(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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is panic the only thing where this the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the only thing I saw in the tests, at least.
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
$crate
paths, which in turn means fewer inconsistencies.DCA looks great; a significant reduction in type inference inconsistencies (down
20,267
from83,257
) and a small performance improvement, at the cost of only a small reduction in resolved calls.