-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Rust: Fallback crate resolution #20225
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
Rust: Fallback crate resolution #20225
Conversation
f1e4bb9
to
8b44182
Compare
4ee31bd
to
8a3c3aa
Compare
245b001
to
4a32fc0
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 introduces fallback crate resolution to handle cases where crate dependency information from the extractor may be incomplete. The key change allows paths to resolve to any crate in the database when the crate name doesn't conflict with known dependencies, and adds consistency improvements for qualified path resolution.
Key changes:
- Adds fallback crate resolution when dependency information is missing
- Implements consistency checks to ensure qualified paths like
q::p
resolve consistently with their qualifiers - Refactors path resolution to separate candidate resolution from final resolution with consistency checks
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
File | Description |
---|---|
PathResolutionConsistency.qll | Updates path resolution consistency check to exclude crate conflicts from multiple resolution warnings |
PathResolution.qll | Major refactoring adding fallback crate resolution, consistency checks, and new implementation classes |
TypeParamImpl.qll | Simplifies type bound resolution by delegating to TypeParamItemNode |
*.expected files | Updated test expectations reflecting reduced path resolution inconsistencies |
// As a fallback, give all files access to crates that do not conflict with known dependencies | ||
// and declarations. | ||
name = dep.getName() and | ||
not declaresDirectly(file, TTypeNamespace(), name) 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.
The TTypeNamespace()
call should likely be name.getNamespace()
or a similar method to get the appropriate namespace for the crate name, rather than always using the type namespace.
not declaresDirectly(file, TTypeNamespace(), name) and | |
not declaresDirectly(file, dep.getNamespace(), name) and |
Copilot uses AI. Check for mistakes.
@@ -1218,7 +1303,7 @@ class RelevantPath extends Path { | |||
pragma[nomagic] | |||
predicate isUnqualified(string name) { | |||
not exists(this.getQualifier()) and | |||
not this = any(UseTreeList list).getAUseTree().getPath() and | |||
not this = any(UseTreeList list).getAUseTree().getPath().getQualifier*() and | |||
name = this.getText() | |||
} | |||
|
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 transitive closure operator *
after getQualifier
creates a potential infinite recursion if there are circular qualifier relationships. Consider using a bounded recursion or a different approach to handle qualifier chains.
not isQualifiedViaUseTree(this) and | |
name = this.getText() | |
} | |
/** | |
* Helper predicate to check if `path` is reachable via qualifiers from any UseTreeList, | |
* using bounded recursion to avoid infinite loops in case of cycles. | |
*/ | |
private predicate isQualifiedViaUseTree(Path path) { | |
exists(UseTreeList list, UseTree ut, Path p | | |
ut = list.getAUseTree() and | |
p = ut.getPath() and | |
isReachableQualifier(p, path, []) | |
) | |
} | |
/** | |
* Bounded recursion: holds if `target` is reachable from `current` via qualifiers, | |
* without revisiting any path in `visited`. | |
*/ | |
private predicate isReachableQualifier(Path current, Path target, Path[] visited) { | |
current = target | |
or | |
not visited.contains(current) and | |
exists(Path next | | |
next = current.getQualifier() and | |
isReachableQualifier(next, target, visited + [current]) | |
) | |
} |
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.
Look good. Great improvement to the resolved calls percentage 🤩
I have left a few comments and questions.
// if `path` is the qualifier of a resolvable `parent`, then we should | ||
// resolve `path` to something consistent with what `parent` resolves to | ||
exists(RelevantPath parent | | ||
resolvePathCandQualified(path, result, parent, _) = resolvePathParent(path, parent) |
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.
resolvePathCandQualified
implies that path
is the qualifier of parent
, so could this be simplified to
resolvePathCandQualified(path, result, parent, _) = resolvePathParent(path, parent) | |
resolvePathCandQualified(path, result, parent, _) = resolvePath(parent) |
and resolvePathParent
removed?
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.
Indeed, good spot.
@@ -748,6 +744,11 @@ private class ImplTraitTypeReprItemNode extends TypeItemNode instanceof ImplTrai | |||
override string getCanonicalPath(Crate c) { none() } | |||
} | |||
|
|||
private class ImplTraitTypeReprItemNodeImpl extends ImplTraitTypeReprItemNode { | |||
pragma[nomagic] | |||
ItemNode resolveABound() { result = resolvePathCand(this.getABoundPath()) } |
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.
I think it's a bit subtle to have Foo.method
do one thing and FooImpl.method
do another thing. What about including cand
in the name similar to resolvePath
and resolvePathCand
? Then this would be resolveABoundCand
. And similar for the other "shadowed" method names?
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.
Good idea.
@@ -241,14 +241,14 @@ abstract class ItemNode extends Locatable { | |||
or | |||
// items made available by an implementation where `this` is the implementing type | |||
exists(ItemNode node | | |||
this = node.(ImplItemNode).resolveSelfTy() and | |||
this = node.(ImplItemNodeImpl).resolveSelfTy() 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 it correct that we're using ImplItemNodeImpl
only because resolveSelfTy
from ImplItemNode
would give non-monotonic recursion? If so, maybe we can write that down somewhere? Perhaps a QLdoc on resolvePathCand
saying that it's used internally to avoid non-monotonic recursion?
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.
Correct.
crateDependency(file, name, dep) | ||
or | ||
// As a fallback, give all files access to crates that do not conflict with known dependencies | ||
// and declarations. |
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.
Maybe mention here that we need the fallback since the extractor based information used in crateDependency
might be incomplete?
4a32fc0
to
f87f52d
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.
Thanks for the PR and for addressing my comments. Looks great 💪
Before this PR, we relied strictly on crate dependency information from the extractor when resolving paths to crates. However, for whatever reason, this information may sometimes be incomplete, so in this PR we relax this condition and allow for a path to resolve to any crate in the DB, as long as the name of that crate does not conflict with the name of a known dependency.
In order to reduce path resolution inconsistencies, this PR also adds a step to make resolution of qualified paths like
q::p
consistent; of all the candidate resolution targets ofq
, only chose the one(s) that are consistent with whatq::p
may resolve to.DCA looks great:
Percentage of calls with call target
increases from 61.52 to 62.44, path resolution inconsistencies are reduced by 41 %, at only a marginal slowdown.