Skip to content

Conversation

hvitved
Copy link
Contributor

@hvitved hvitved commented Sep 29, 2025

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-actions github-actions bot added the Rust Pull requests that update Rust code label Sep 29, 2025
@hvitved hvitved force-pushed the rust/macro-call-resolution branch 4 times, most recently from fd9f2fb to ab4e8bc Compare September 29, 2025 18:41
@hvitved hvitved force-pushed the rust/macro-call-resolution branch 4 times, most recently from d43b6d2 to 89b33ab Compare September 30, 2025 13:43
@hvitved hvitved force-pushed the rust/macro-call-resolution branch from 89b33ab to 701cff3 Compare September 30, 2025 14:22
@hvitved hvitved 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 owner September 30, 2025 14:25
Copy link
Contributor

@Copilot Copilot AI left a 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

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

Copy link
Preview

Copilot AI Sep 30, 2025

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
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.

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()
}
Copy link
Preview

Copilot AI Sep 30, 2025

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, _)
)
}
Copy link
Preview

Copilot AI Sep 30, 2025

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()
Copy link
Preview

Copilot AI Sep 30, 2025

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(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.

Copy link
Contributor

@paldepind paldepind left a 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) {
Copy link
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
private predicate isMacroExpansion(AstNode expansion, Path macroDefPath) {
private predicate isMacroExpansion(Path macroDefPath, AstNode expansion) {

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
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
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
Copy link
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
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
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
Copy link
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
Contributor Author

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 paldepind October 1, 2025 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-change-note-required This PR does not need a change note Rust Pull requests that update Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants