Rust: Rework call resolution and type inference for calls#20282
Merged
hvitved merged 9 commits intogithub:mainfrom Oct 22, 2025
Merged
Rust: Rework call resolution and type inference for calls#20282hvitved merged 9 commits intogithub:mainfrom
hvitved merged 9 commits intogithub:mainfrom
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR rewrites how we do call resolution and type inference for calls, to make it more faithful to what actually happens in the compiler.
Impact
The changes to expected test output shows that this PR resolves many shortcomings, as well as removes a lot of inconsistencies.
DCA is excellent: On some projects we achieve a whopping ~90 % reduction in analysis time, which follows the decrease in
Nodes With Type At Length Limitfor those projects. Oncoreutils,rendiation,peace,ruff, andgluon, however, we see increases in both analysis time andNodes With Type At Length Limit.I also did a QA run, which confirms the overall reduction in analysis time:
Top 50 largest absolute deltas
The QA run also showed that we have resolved analysis timeouts/failures for 29 projects:
Projects timeout/failure before
williamlion218/rust-sgx-sdk
zkMIPS/zkMIPS
zama-ai/tfhe-rs
veloren/veloren
kentakom1213/kyopro
TimTheBig/geo-3d
Univa/rumcake
10XGenomics/cellranger
ricosjp/truck
okaponta/atcoder-rust
jblindsay/whitebox-tools
futureversecom/trn-seed
mycroft/challenges
galacticcouncil/hydration-node
ChristopherBiscardi/advent-of-code
gasp-xyz/gasp-monorepo
dimforge/nalgebra
Apollo-Lab-Yale/apollo-rust
awsdocs/aws-doc-sdk-examples
rickyota/genoboost
SparkyPotato/radiance
strawlab/strand-braid
10XGenomics/spaceranger
sarah-quinones/faer-rs
hackmad/pbrt-v3-rs
attack68/rateslib
wingrew/thcore
Gleb-Zaslavsky/RustedSciThe
feos-org/feos
However, timeouts/failures have been introduced for 7 new projects
Projects timeout/failure after
golemfactory/yagna
carthage-software/mago
MaterializeInc/materialize
stencila/stencila
typedb/typedb
Feodor2/Mypal68
mattwparas/steel
In summary, both DCA and QA indicate overall performans wins, which is not necessarily expected (and certainly not the case for many earlier iterations of this PR), as this PR extends on the kinds of calls we are able to resolve.
For the reviewer
Note for review: As usual, commit-by-commit review is encouraged. As for the changes to
TypeInference.qll, I very much recommend using split diff view.Method call resolution
According to the spec, when resolving a method call
x.m():Before this PR, we handled the above in a very ad hoc way, where we did attempt to model implicit dereferencing and borrowing, but we did not model the construction of candidate receiver types and prioritized lookup order. In particular, if
xhad type&Foo, we would only lookup the method inFoo.With this PR, we model prioritized method lookup in the list of candidate receiver types in the module
MethodResolution, but instead of constructing the full list of candidate receiver types, we recursively compute a set of candidates, only adding a new candidate receiver type to the set when we can rule out that the method cannot be found for the current candidate:Care must be taken to ensure that the
not current_candidate matches methodcheck is monotonic, which we achieve using the monotonicisNotInstantiationOfpredicate from the shared type inference library.Method lookup
For a given candidate receiver type
C, we need to match that type against the type of theselfparameters of all potential call targets, taking into account thatselfparameters can have both explicit types and use shorthand syntax. Further care must be taken for methods that are inherited (either a trait method with a default implementation inherited by animplblock or a trait method inherited by a sub trait), so it only makes sense to talk about the type of aselfparameter in the context of a givenimplblock or trait where that method is available (either directly or inherited). We model this using the classAssocFunctionTypein the newly introducedFunctionType.qlllibrary.As before this PR, we use the
IsInstantiationOflibrary for matchingCagainst a givenAssocFunctionTypetypeS, now distinguishing between the following three cases:Cthat matches the blanket type parameter also satisfies the blanket constraint. This means that blanket implementations are now also taken into account in the context of auto-dereferencing/borrowing.Srepresents the type of aselfparameter for a method in a trait: In caseCis e.g.dyn Trait, then we wantCto matchS, but only if the traits match up as well. We achieve this by substituting in the trait in bothSandCbefore performing theIsInstantiationOfcheck.IsInstantiationOfcheck.Method call type inference
When we have identified a valid call target for
x.m()with a given candidate receiver typeC, we need to use that type as well when doing type inference. Before this PR, we used theMatchingmodule from the shared type inference library, but now we instead useMatchingWithEnvironment, where we recordCin the environment via the sequence of auto-dereferences and borrows that happened to obtainC. This means we replace the ad hoc handling mentioned earlier, because we now have explicit knowledge about dereferencing/borrowing. The implementation is in the newMethodCallMatchingInputmodule.Non-method call resolution
Resolution of non-method calls is much easier, since there is no such thing as auto-dereferencing and borrowing, even if the target is a method (
Foo::m(&x)vsx.m()). However, as for method call resolution, we still need to take three cases into account:IsInstantiationOfcheck.IsInstantiationOfcheck, using an argument or the call context, when it provides information about the return type.The implementation is in the module
NonMethodResolutionfor calls that target non-methods, and in theMethodResolutionmodule for calls that target methods.Non-method call type inference
When the call is an operator call, we need to take into account that implicit borrowing may happen. For example,
x == yis syntactic sugar forPartialEq::eq(&x, &y), so in order for the types to properly match up, we adjust the types of the operator, by stripping away the&s. This is done in the newOperationMatchingInputmodule.When the call is not an operator call, we can match types directly, which happens in the
NonMethodCallMatchingInputmodule for calls that target non-methods, and in theMethodCallMatchingInputmodule for calls that target methods.Future work
Dereftrait when performing auto-dereferencing and unsized coercions. With this PR, however, it should be much easier to support that.C_i, we will still lookup inC_(i+1)as well. Supporting this is not straightforward, since we need a monotonic way of checking blanket constraint non-satisfaction.