Skip to content

Rust: Macro call resolution - #20548

Merged
hvitved merged 4 commits into
github:mainfrom
hvitved:rust/macro-call-resolution
Oct 2, 2025
Merged

Rust: Macro call resolution#20548
hvitved merged 4 commits into
github:mainfrom
hvitved:rust/macro-call-resolution

Conversation

@hvitved

@hvitvedhvitved commented Sep 29, 2025

Copy link
Copy Markdown
Contributor

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 from 83,257) and a small performance improvement, at the cost of only a small reduction in resolved calls.

@github-actionsgithub-actionsBot added the Rust Pull requests that update Rust code label Sep 29, 2025
@hvitved
hvitvedforce-pushed the rust/macro-call-resolution branch 4 times, most recently from fd9f2fb to ab4e8bcCompareSeptember 29, 2025 18:41
Comment threadrust/ql/lib/codeql/rust/internal/PathResolution.qll Fixed
@hvitved
hvitvedforce-pushed the rust/macro-call-resolution branch 4 times, most recently from d43b6d2 to 89b33abCompareSeptember 30, 2025 13:43
@hvitved
hvitvedforce-pushed the rust/macro-call-resolution branch from 89b33ab to 701cff3CompareSeptember 30, 2025 14:22
@hvitvedhvitved added the no-change-note-required This PR does not need a change note label Sep 30, 2025
@hvitved
hvitved marked this pull request as ready for review September 30, 2025 14:25
@hvitved
hvitved requested a review from a team as a code ownerSeptember 30, 2025 14:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
PathResolution.qllCore implementation of macro namespace, macro item nodes, and enhanced path resolution logic
PathResolutionConsistency.qllUpdated consistency checks to handle macro resolution improvements
MacroCallImpl.qllAdded macro resolution capability to MacroCall AST nodes
PathResolutionInlineExpectationsTest.qllEnhanced test filtering to exclude macro-specific test markers
Various .expected filesUpdated test expectations reflecting improved macro resolution accuracy
Various test source filesAdded macro resolution test annotations and examples

Comment on lines 196 to +210

abstract Attr getAnAttr();

pragma[nomagic]
final Attr getAttr(string name) {
result = this.getAnAttr() and
result.getMeta().getPath().(RelevantPath).isUnqualified(name)
}

CopilotAISep 30, 2025

Copy link

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.

Suggested change
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.
*/

Copilot uses AI. Check for mistakes.
Comment on lines +683 to +688
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()
}

CopilotAISep 30, 2025

Copy link

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.
Comment on lines +1844 to 1855
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, _)
)
}

CopilotAISep 30, 2025

Copy link

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.
Comment on lines +1699 to +1701
exists(Path macroDefPath |
isInMacroExpansion(macroDefPath, n) and
crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile()

CopilotAISep 30, 2025

Copy link

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.

Suggested change
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

Copilot uses AI. Check for mistakes.

@paldepindpaldepind left a comment

Copy link
Copy Markdown
Contributor

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) {

Copy link
Copy Markdown
Contributor

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.

Suggested change
privatepredicateisMacroExpansion(AstNodeexpansion,PathmacroDefPath){
privatepredicateisMacroExpansion(PathmacroDefPath,AstNodeexpansion){

Comment threadrust/ql/lib/codeql/rust/internal/PathResolution.qll Outdated
Comment on lines +1698 to +1701
predicate isInMacroFromCrateExpansion(CrateItemNode crate, AstNode n) {
exists(Path macroDefPath |
isInMacroExpansion(macroDefPath, n) and
crate.getASourceFile().getFile() = resolvePathCand(macroDefPath).getFile()

Copy link
Copy Markdown
Contributor

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?

Suggested change
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

Copy link
Copy Markdown
Contributor

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.

Suggested change
exists(ns) and

|
pathUsesNamespace(path, ns)
or
not pathUsesNamespace(path, _)

Copy link
Copy Markdown
Contributor

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.

Suggested change
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

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
ContributorAuthor

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.

@hvitved
hvitved requested a review from paldepindOctober 1, 2025 12:46

@paldepindpaldepind left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
predicateisInDollarCrateSupportedMacroExpansion(FilemacroDefFile,AstNoden){
privatepredicateisInDollarCrateSupportedMacroExpansion(FilemacroDefFile,AstNoden){

* calls.
*/
pragma[nomagic]
predicate resolveDollarCrate(RelevantPath p, CrateItemNode crate) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
predicateresolveDollarCrate(RelevantPathp,CrateItemNodecrate){
privatepredicateresolveDollarCrate(RelevantPathp,CrateItemNodecrate){

macroDefFile = resolvePathCand(macroDefPath).getFile()
)
or
isInDollarCrateSupportedMacroExpansion(macroDefFile, n.getParentNode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. a predicate from $crate paths to their enclosing expansion and
  2. 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.

@hvitvedhvitvedOct 2, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll use doublyBoundedFastTC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, after looking up what doublyBoundedFastTC I think that approach is really clean!

@hvitved

Copy link
Copy Markdown
ContributorAuthor

Why do path resolution inconsistencies increase for some projects? For instance, Tokio increases from 655 to 9030.

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

Copy link
Copy Markdown
Contributor

This happens because we are now also resolving paths to macros, and in some cases those are not unique. We could investigate follow-up.

Thanks. That seems reasonable :)

@paldepind

Copy link
Copy Markdown
Contributor

I've approved, but maybe we need another DCA run?

@hvitved
hvitved merged commit b4c979f into github:mainOct 2, 2025
19 checks passed
@hvitved
hvitved deleted the rust/macro-call-resolution branch October 2, 2025 12:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-requiredThis PR does not need a change noteRustPull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@hvitved@paldepind@github-advanced-security