Skip to content

Conversation

@hvitved
Copy link
Contributor

@hvitved hvitved commented Dec 12, 2025

Trait methods with default implementations have their types specialized when performing method resolution, that is, when matching the type of a receiver against the type of the self parameter. This PR does the same when inferring types of calls, for example in

let x : i32 = 0;
let y : usize = 1;
let z = x.max(y);

the target of x.max(y) is https://doc.rust-lang.org/std/cmp/trait.Ord.html#method.max, and before this PR the type of the other parameter would remain Self, which meant that z would have both types i32 and usize. After this PR, the type of other is specialized to i32, which means z will only have type i32.

DCA is uneventful.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Dec 12, 2025
@hvitved hvitved force-pushed the rust/type-inference-matching-specialization branch from 3776f1f to 846ae48 Compare December 12, 2025 18:48
@hvitved hvitved added the no-change-note-required This PR does not need a change note label Dec 15, 2025
@hvitved hvitved force-pushed the rust/type-inference-matching-specialization branch from 846ae48 to 14344e7 Compare December 15, 2025 08:05
@hvitved hvitved marked this pull request as ready for review December 15, 2025 09:20
@hvitved hvitved requested a review from a team as a code owner December 15, 2025 09:20
@hvitved hvitved requested review from Copilot and paldepind December 15, 2025 09:20
Copy link
Contributor

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 enhances type inference for trait method calls by using specialized types when a trait method with a default implementation is invoked. Previously, type parameters like Self remained unresolved in parameter types, leading to ambiguous type inference. Now, these type parameters are specialized to their concrete types (e.g., Self becomes i32 when called on an i32 value), improving type inference accuracy.

Key changes:

  • Introduced FunctionDeclaration class to handle both free functions and associated functions with optional trait/impl context
  • Refactored method and non-method call matching to use specialized declaration types that track the impl/trait context
  • Simplified InvokedClosureExpr by leveraging existing DynamicCallExpr logic

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
rust/ql/test/library-tests/type-inference/main.rs Removed obsolete comment about missing type inference for MyOption<S>, now correctly inferred
rust/ql/test/library-tests/type-inference/CONSISTENCY/PathResolutionConsistency.expected Updated line numbers to reflect comment removal from main.rs
rust/ql/lib/codeql/rust/internal/TypeInference.qll Core implementation: added FunctionDeclaration abstraction, refactored matching modules to track impl/trait context for type specialization

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@paldepind
Copy link
Contributor

I think I need to understand the motivation behind this a bit better.

The example doesn't type check 😉 and it's not entirely clear that the current behavior is wrong.

The type of max is fn max<Self>(self: Self, other: Self) -> Self (with the implicit Self type parameter written out). So the arguments and the return value should all have the same type and if we end up in a situation where the arguments have different types, then something has gone wrong (ignoring coercions). In that case, instantiating the type parameter with both types doesn't seem that unreasonable, as either type could be the right one. Like in this example where the correct type of z is usize:

let x = 0;
let y: usize = 1;
let z = x.max(y);

I think it would be great to update the PR with some tests that show the difference that this PR makes. I guess the good cases might involve implicit coercions in the argument?

@hvitved hvitved force-pushed the rust/type-inference-matching-specialization branch from 14344e7 to 86a4d42 Compare December 15, 2025 12:11
@hvitved
Copy link
Contributor Author

hvitved commented Dec 15, 2025

I think I need to understand the motivation behind this a bit better.

Sorry, I should have mentioned: It is to avoid inference explosion on #20973.

The example doesn't type check 😉 and it's not entirely clear that the current behavior is wrong.

Sorry about that; I have (force) pushed a test, so we can see the effect of this PR.

The type of max is fn max<Self>(self: Self, other: Self) -> Self (with the implicit Self type parameter written out). So the arguments and the return value should all have the same type and if we end up in a situation where the arguments have different types, then something has gone wrong (ignoring coercions). In that case, instantiating the type parameter with both types doesn't seem that unreasonable, as either type could be the right one. Like in this example where the correct type of z is usize:

My reasoning here is that since we use the type of the receiver to lookup method call targets, then it should be the receivers alone that determine the value of the Self type parameter.

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