Skip to content

Rust: Type inference for .await expressions #19584

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

hvitved
Copy link
Contributor

@hvitved hvitved commented May 26, 2025

Adds type inference support for .await expressions. Since functions can be either implicitly asynchronous, using the async keyword, or explicitly using an impl Future<Output = ...> return type, this PR also add support for associated type argument (Output = ...) and impl Trait types.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label May 26, 2025
@hvitved hvitved force-pushed the rust/type-inference-await branch 2 times, most recently from 92e4659 to 25b928d Compare May 27, 2025 11:59
@hvitved hvitved marked this pull request as ready for review May 28, 2025 06:32
@Copilot Copilot AI review requested due to automatic review settings May 28, 2025 06:32
@hvitved hvitved requested a review from a team as a code owner May 28, 2025 06:32
@hvitved hvitved requested a review from paldepind May 28, 2025 06:32
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 enhances the Rust type inference engine to support .await expressions, associated type arguments (Output = ...), and impl Trait types.

  • Add new async_ and impl_trait tests exercising .await and impl Trait inference.
  • Update expected outputs for type‐inference tests to include await and impl Trait cases.
  • Extend QL libraries (TypeMention.qll, TypeInference.qll, Type.qll, Stdlib.qll) to resolve associated types, impl Trait parameters, and infer await expressions.

Reviewed Changes

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

Show a summary per file
File Description
rust/ql/test/library-tests/type-inference/main.rs Add async_ and impl_trait modules and invoke new test functions
rust/ql/test/library-tests/type-inference/type-inference.expected Update expected inference output to cover .await and impl Trait cases
rust/ql/lib/codeql/rust/internal/TypeMention.qll Support associated type arguments and impl Trait mentions in types
rust/ql/lib/codeql/rust/internal/TypeInference.qll Implement inferAwaitExprType, extend method resolution for impl Trait
rust/ql/lib/codeql/rust/internal/Type.qll Define TImplTraitType, TImplTraitTypeParameter, and related classes
rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll Introduce FutureTrait with its Output associated type
Comments suppressed due to low confidence (2)

rust/ql/lib/codeql/rust/internal/TypeInference.qll:1195

  • [nitpick] This only resolves the first impl Trait bound. For multi-bound impl Trait types, consider iterating over all TImplTraitTypeParameter(_, _) parameters so methods on any bound are discovered.
result = getTraitMethod(mc.getTypeAt(TypePath::singleton(TImplTraitTypeParameter(_, _))), mc.getMethodName())

rust/ql/lib/codeql/rust/internal/TypeInference.qll:248

  • In the typeEquality predicate, matching n2 = any(BlockExpr be | ...) could match unrelated block expressions. It should constrain be to the actual n2 (e.g. be = n2.(BlockExpr)) to avoid spurious matches.
n2 =

@@ -77,6 +77,16 @@ private module Input1 implements InputSig1<Location> {
apos.asMethodTypeArgumentPosition() = ppos.asTypeParam().getPosition()
}

private int getImplTraitTypeParameterId(ImplTraitTypeParameter tp) {
Copy link
Preview

Copilot AI May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getImplTraitTypeParameterId function uses rank[result] but never assigns result. To return the intended integer ID, add result = id after the rank expression or switch to rank[id].

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.

Since impl types implement traits we should have a case for them in conditionSatisfiesConstraint. It would also be great to have a test covering that. Something like the following should pass with impl added to conditionSatisfiesConstraint:

trait MyTrait<A> {
    fn get_a(&self) -> A;
}

impl MyTrait<S2> for S1 {
    fn get_a(&self) -> S2 {
        S2
    }
}

fn get_a_my_trait() -> impl MyTrait<S2> {
    S1
}

fn uses_my_trait<A, B: MyTrait<A>>(t: B) -> A {
    t.get_a() // $ method=MyTrait::get_a
}

fn test() {
    let a = get_a_my_trait();
    let b = uses_my_trait(a); // $ type=b:S2
}

* We represent `impl Trait` types as generic types with as many type parameters
* as there are bounds.
*
* [1] https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to link to the reference than to the book.

Suggested change
* [1] https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
* [1] https://doc.rust-lang.org/reference/types/impl-trait.html

Comment on lines 18 to 20
TImplTraitType(int bounds) {
bounds = any(ImplTraitTypeRepr impl).getTypeBoundList().getNumberOfBounds()
} or
Copy link
Contributor

@paldepind paldepind May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have two functions like

fn f() -> impl MyTrait { ..  }

fn g() -> impl MyTrait { .. }

then they return two different types (in the general case) even though they are syntactically identical.

Given that wouldn't it be more accurate to represent impl types like:

Suggested change
TImplTraitType(int bounds) {
bounds = any(ImplTraitTypeRepr impl).getTypeBoundList().getNumberOfBounds()
} or
TImplTraitType(ImplTraitTypeRepr impl) or

I.e., every impl is it own types, similar to how every struct or enum is its own type. I think this would also simplify things a bit as the impl types wouldn't need to have type parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for suggesting this, which I have implemented. I also learned that impl Trait types in parameter type positions are very different from impl Trait types in return type positions, so I have reworked the implementation to respect that (the former corresponds to a type parameter, the latter to an abstract type).

not be.isAsync() and
n1 = be.getStmtList().getTailExpr() and
path1 = path2
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using exists here would be more consistent with the surrounding code.

pub async fn f() {
f1().await.f(); // $ method=S1f
f2().await.f(); // $ method=S1f
f3().await.f(); // $ method=S1f
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to test this case as well:

Suggested change
f3().await.f(); // $ method=S1f
f3().await.f(); // $ method=S1f
S2.await.f(); // $ MISSING: method=S1f

not exprPath
.isCons(TImplTraitTypeParameter(_, _),
any(TypePath path0 | path0.isCons(getFutureOutputTypeParameter(), _)))
)
Copy link
Contributor

@paldepind paldepind May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct that this covers most of the special cases that occurs in practice but doesn't handle the general case where the awaited expression is some arbitrary type that implements Future?

It might be nice to add some comments about which cases are covered and about what is not covered.

For full generality I guess that we might have to expose conditionSatisfiesConstraintTypeAt in some form or implement await using the Matching module? (EDIT: Just to be clear I'm not saying we should do that.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reimplemented this.

@hvitved hvitved force-pushed the rust/type-inference-await branch from 25b928d to 80d91e2 Compare June 2, 2025 19:29
@hvitved hvitved added the no-change-note-required This PR does not need a change note label Jun 2, 2025
@hvitved hvitved force-pushed the rust/type-inference-await branch from 80d91e2 to 14b8af8 Compare June 3, 2025 07:26
@hvitved hvitved force-pushed the rust/type-inference-await branch from 14b8af8 to 15ca62e Compare June 3, 2025 07:50
@hvitved hvitved requested a review from paldepind June 3, 2025 08:08
@hvitved
Copy link
Contributor Author

hvitved commented Jun 3, 2025

@paldepind : PR is ready for review again. I have rebased to resolve merge conflicts; only the last two commits need reviewing.

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