Skip to content

Conversation

@hvitved
Copy link
Contributor

@hvitved hvitved commented Oct 23, 2025

Type propagation into arguments is needed in cases like

let x = Vec::new();
x.push(0); // the type `i32` must propagate into `x` to infer that `x` has type `Vec<i32>`

Up until now, we have unconditionally propagated types into all arguments, which can sometimes lead to explosions in inferred types.

This PR circumvents such explosions by only propagating type information into arguments when those arguments are potentially context-typed (such as Vec::new and Default::default).

DCA looks great: we almost get the Nodes With Type At Length Limit count down to 0, sacrificing only a relatively small amount of resolvable calls.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Oct 23, 2025
tp instanceof TSelfTypeParameter and
exists(getCallExprTypeQualifier(a, _))
)
) and

Check warning

Code scanning / CodeQL

Omittable 'exists' variable Warning

This exists variable can be omitted by using a don't-care expression
in this argument
.
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch 5 times, most recently from 9097870 to a5e8fdb Compare October 27, 2025 12:41
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch 6 times, most recently from e05b287 to fb8aabc Compare October 31, 2025 13:35
@hvitved hvitved added the no-change-note-required This PR does not need a change note label Nov 3, 2025
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch from fb8aabc to 2d5aecf Compare November 3, 2025 07:34
@hvitved hvitved marked this pull request as ready for review November 3, 2025 07:37
@hvitved hvitved requested a review from a team as a code owner November 3, 2025 07:37
@hvitved hvitved requested review from Copilot and paldepind November 3, 2025 07:37
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 introduces context-based typing for Rust function calls to improve type inference accuracy and reduce combinatorial explosions. The changes implement a mechanism to track when function return types depend on context (like Default::default() or Vec::new()) rather than their arguments.

Key Changes

  • Introduced a new TContextType type to mark context-typed expressions
  • Added ContextTyping module to identify and handle context-typed calls
  • Modified type inference to prevent type information from flowing into arguments unless they are context-typed
  • Updated test expectations to reflect improved type inference results

Reviewed Changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
rust/ql/lib/codeql/rust/internal/Type.qll Added new TContextType and ContextType class for context-based typing
rust/ql/lib/codeql/rust/internal/TypeInference.qll Implemented ContextTyping module and integrated context checking into method and non-method call type inference
rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll Filtered out TContextType in argument substitution
rust/ql/lib/codeql/rust/internal/typeinference/BlanketImplementation.qll Filtered out TContextType in blanket implementation type resolution
rust/ql/test/library-tests/type-inference/type-inference.ql Added import and filter to exclude TContextType from test output
rust/ql/test/library-tests/type-inference/main.rs Added test cases for context-typed calls with trait implementations
Various *.expected files Updated expected test results reflecting improved type inference

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

@paldepind
Copy link
Contributor

I need to better understand how this relates to the existing certain type machinery and what we want to do going forward.

There clearly is some overlap between the two things. With certain types we can find places where we need to do speculative inference with:

not exists(infersCertainTypeAt(n, path)) }

And with context types we can find places where we need to do speculative inference with:

inferType(n, path) = TContextType()

In the former case we're marking places with certainty and in the latter we're marking places with uncertainty. In the former case, too few marking leads to too many types (over approximation) and in the latter case too few markings leads to too few types (under approximation).

It seems that certain type inference can block spurious types in ways that context types cannot:

struct MyA {}
struct MyB {}

impl Default for MyA { fn default() -> Self { MyA {} } }

impl Deref for MyA {
    type Target = MyB;
    fn deref(&self) -> &Self::Target { &MyB {} }
}

fn take_a(_b: &MyA) {}
fn take_b(_b: &MyB) {}

fn test() {
    let x: MyA = Default::default(); // Certain types prevent `x : MyB`
    take_a(&x);
    take_b(&x);

    let y = Default::default(); // Context types can not prevent `x : MyB`
    take_a(&y);
    take_b(&y);
}

So maybe we could instead expand on the existing certain type inference? Or are there things that context type can prevent that certain types cannot? Having an example of such a thing as a test would be great.

We should also infer context types for:

  • Integer literals — the type of i could depend on the call in let i = 0; f(i).
  • Constructor calls/struct expressions with generics — the generic in m could depend on the call in let n = None; f(n).

@hvitved
Copy link
Contributor Author

hvitved commented Nov 3, 2025

Or are there things that context type can prevent that certain types cannot? Having an example of such a thing as a test would be great.

Anything involving method calls will be difficult to support, since we cannot monotonically determine when a method call has a unique target and hence has a unique return type.

@hvitved
Copy link
Contributor Author

hvitved commented Nov 3, 2025

We should also infer context types for:

  • Integer literals — the type of i could depend on the call in let i = 0; f(i).

Right now i would be getting two types (if the type of f's argument is not i32), are you suggesting we keep it that way?

  • Constructor calls/struct expressions with generics — the generic in m could depend on the call in let n = None; f(n).

Yes, your are right, I forgot about those.

@hvitved hvitved marked this pull request as draft November 3, 2025 20:28
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch from fca1614 to d43133f Compare November 4, 2025 10:02
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch 2 times, most recently from f7e5281 to cdf6c86 Compare November 4, 2025 15:24
@hvitved hvitved force-pushed the rust/type-inference-arg-target-typed branch from cdf6c86 to bd8665a Compare November 4, 2025 15:39
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