-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Rust: Implement type inference for closures and calls to closures #20130
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
base: main
Are you sure you want to change the base?
Conversation
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 implements type inference for closure expressions and calls to closures in Rust, including proper handling of FnOnce
trait bounds. The implementation reuses tuple types for function parameters and represents closures as dyn FnOnce
types.
- Adds type inference support for closure expressions, their parameters, and return types
- Implements type propagation for calls to closures and higher-order functions
- Introduces handling of the
FnOnce
trait with syntactic sugar for function types
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
closure.rs | New test file with comprehensive closure type inference test cases |
main.rs | Removes old closure test module and adds reference to new test file |
TypeMention.qll | Adds support for parenthesized argument lists and FnOnce trait handling |
TypeInference.qll | Implements core closure type inference logic and call expression handling |
Stdlib.qll | Defines the FnOnce trait class for type system integration |
type-inference.expected | Expected test output showing inferred types for closure expressions |
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.
Some questions to make sure I'm understanding things.
The relatively small improvement in DCA suggests there may still be gaps to get coverage of the most common cases???
mod simple_closures { | ||
pub fn test() { | ||
// A simple closure without type annotations or invocations. | ||
let my_closure = |a, b| a && b; |
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.
Does this test case show us anything?
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.
It shows (in the expected file) what types will be inferred for a simple closure that isn't called.
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.
Ah, shame it doesn't show up in an annotation, but 👍.
@@ -2459,46 +2459,7 @@ pub mod pattern_matching_experimental { | |||
} | |||
} | |||
|
|||
mod closures { |
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.
Why have these test cases been deleted?
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 the stuff being tested by these examples are covered by the new tests for closures. There was quite a lot of "extra" setup in these tests, for what was actually being tested, so I think the new tests are preferable.
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.
Yes they were somewhat close to some real world examples I'd encountered. I guess I'd like reassuring that they would indeed work right now, whether or not they're part of our test suite.
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.
Sorry, I should have been more clear. They unfortunately still don't work, due to the "Another reason" that I mention in #20130 (comment).
The case is covered by the
let f = |x| x + 1; // $ MISSING: type=x:i64 target=add
let _r2 = apply_two(f); // $ target=apply_two type=_r2:i64
test, which is essentially a "minimal" variant of the existing test. It test passing an unannotated closure to a function with a known type.
) | ||
or | ||
// _If_ the invoked expression has the type of a closure, then we propagate | ||
// the surrounding types into the closure. |
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'd like to understand how these differs from the cases above a bit better.
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 key difference is the direction.
- The cases above propagate type information out from the invoked expression (which implements
FnOnce
) to the arguments and the entire call. - The cases below propagate type information in to the invoked expression. This however we only do if the invoked expression has a closure type (isn't some other type that implements
FnOnce
).
Propagating type information into the invoked expression for arbitrary FnOnce
implementations would make sense, but we don't have that machinery as of right now.
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.
Great, thanks for the explanation.
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
My guess is that a big reason is that we only support Another reason could be that we can't infer the type of a closure from it being passed to an annotated function. This is shown on line 62 in the test and is an instance of a more general problem: When an argument is passed to a function where the function expects the argument to implement some trait, we don't propagate information from the trait bound back to the argument. This affects closures a lot since they are always given as trait bounds on parameter types. |
This PR implements type inference for closure expressions, calls to closures, and correctly handles
FnOnce
trait bounds.A few notes on the implementation and the PR:
FnOnce
trait has a type parameterArgs
which is a tuple of all the arguments. That is reflected in our implementation as well, i.e., we re-use our tuple types for parameters.FnOnce
,FnMut
, andFn
traits. They form a trait hierarchy withFnOnce
at the top.FnOnce
uses an associated type for the return type and since we don't support associated types inherited in subtraits (tests for this was added in Rust: Fix type inference for trait objects for traits with associated types #20122) we can right now only really understand theFnOnce
trait and not the others.dyn FnOnce
type. That is, instead of creating some new type for closures we just reusedyn
. The Rust language itself says that closures have some internal type that can't be written by users, and all that known about it is that it implements some of theFnOnce
/Fn
/FnMut
traits.dyn
gives us this effect. rust-analyzer shows closures as having inimpl FnOnce
type. I think that makes a bit more sense thandyn
, but usingdyn
was simpler.Fn
trait for closures, as it is a subtrait of the others, but, again, we can't understandFn
yet.DCA seems fine, though we only get a 0.04% point increase in resolved calls, which is less than I had hoped.