[ty] Apply type mappings to functions eagerly#20596
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
CodSpeed WallTime Performance ReportMerging #20596 will improve performances by 6.85%Comparing Summary
Benchmarks breakdown
|
CodSpeed Instrumentation Performance ReportMerging #20596 will not alter performanceComparing Summary
Footnotes
|
| pub struct FunctionType<'db> { | ||
| pub(crate) literal: FunctionLiteral<'db>, |
There was a problem hiding this comment.
We might also consider consolidating FunctionType and FunctionLiteral into a single type now. (FunctionLiteral would have the extra fields that hold the updated signatures.)
| pub(super) fn walk_function_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( | ||
| db: &'db dyn Db, | ||
| function: FunctionType<'db>, | ||
| visitor: &V, | ||
| ) { | ||
| walk_function_literal(db, function.literal(db), visitor); | ||
| for mapping in function.type_mappings(db) { | ||
| walk_type_mapping(db, mapping, visitor); | ||
| } | ||
| } |
There was a problem hiding this comment.
do we not need to walk the new fields here (updated_signature and updated_last_definition_signature)? Would it be worth adding a comment about why that's not necessary?
There was a problem hiding this comment.
Took me quite a bit of time to construct an example where this would have any observable effects, since you first need to apply a type mapping to the function literal and then have a type-visitor walk over it:
from typing import cast
from ty_extensions import TypeOf, Unknown
class C[T]:
def f(self, t: T) -> T | Unknown:
raise NotImplementedError
cast(TypeOf[C[int]().f], C[int]().f)I'm first applying a specialization type mapping through C[int]().f, and then a type visitor via any_over_type(..., contains_unknown_or_todo, ..) by attempting a cast. Without walking over the updated_* fields, we get a
Value is already of type `bound method C[int].f(t: int) -> int | Unknown`
diagnostic (which shouldn't happen, if Unknown is part of the type). After applying the following patch, the diagnostic is gone.
@@ -692,6 +692,14 @@ pub(super) fn walk_function_type<'db, V: super::visitor::TypeVisitor<'db> + ?Siz
visitor: &V,
) {
walk_function_literal(db, function.literal(db), visitor);
+ if let Some(callable_signature) = function.updated_signature(db) {
+ for signature in &callable_signature.overloads {
+ walk_signature(db, signature, visitor);
+ }
+ }
+ if let Some(signature) = function.updated_last_definition_signature(db) {
+ walk_signature(db, signature, visitor);
+ }
}
Is this ecosystem change correct? If so, can we add an mdtest making sure it doesn't regress? |
carljm
left a comment
There was a problem hiding this comment.
This looks great! (Modulo existing inline comments.) Thank you!
| pub(crate) updated_signature: Option<CallableSignature<'db>>, | ||
| #[returns(as_ref)] | ||
| pub(crate) updated_last_definition_signature: Option<Signature<'db>>, |
There was a problem hiding this comment.
Maybe some doc comments here explaining what these fields are and when/how they become populated?
There was a problem hiding this comment.
I added some doc comments and made those fields non-public. @dcreager please feel free to change them in a follow-up, if something seems wrong/missing. I'd like to merge this PR to fix astral-sh/ty#1261.
This comment was marked as resolved.
This comment was marked as resolved.
|
Glad I looked at my notifications this morning before continuing to work on astral-sh/ty#1261! The lazy application of type mappings to functions (and the application of type mappings to those lazy type mappings) seemed to be the root cause of that issue, and applying type-mappings eagerly would have been one of the attempts to solve that bug. And indeed, the problem is completely solved with this PR!
|
a3e8974 to
cec807d
Compare
from typing import Any, ParamSpec, TypeVar
from inspect import isawaitable
from collections.abc import Callable, Coroutine
T = TypeVar("T")
P = ParamSpec("P")
async def ensure_async(
f: Callable[P, T] ,
*args: P.args,
**kwargs: P.kwargs,
) -> T:
rv = f(*args, **kwargs)
if isawaitable(rv):
rv = await rv
return rvYes, this is a false positive that shouldn't be there. I invested quite a bit of time trying to track it down, and while I haven't fully understood what is going on, it feels like it's not worth pursuing further (given that the behavior arguably improves). First of all, it took me a while to even reproduce it, because it only happens when checking on 3.12, not on 3.13 (which I use by default for testing locally). There are a lot of |
AlexWaygood
left a comment
There was a problem hiding this comment.
Thank you!
I wondered if we should add tests for #20596 (comment) and/or #20596 (comment), but they both seem obscure/strange enough that it would probably be quite low-value to do so :)
* main: (21 commits) [ty] Literal promotion refactor (#20646) [ty] Add tests for nested generic functions (#20631) [`cli`] Add conflict between `--add-noqa` and `--diff` options (#20642) [ty] Ensure first-party search paths always appear in a sensible order (#20629) [ty] Use `typing.Self` for the first parameter of instance methods (#20517) [ty] Remove unnecessary `parsed_module()` calls (#20630) Remove `TextEmitter` (#20595) [ty] Use fully qualified names to distinguish ambiguous protocols in diagnostics (#20627) [ty] Ecosystem analyzer: relax timeout thresholds (#20626) [ty] Apply type mappings to functions eagerly (#20596) [ty] Improve disambiguation of class names in diagnostics (#20603) Add the *The Basics* title back to CONTRIBUTING.md (#20624) [`playground`] Fix quick fixes for empty ranges in playground (#20599) Update dependency ruff to v0.13.2 (#20622) [`ruff`] Fix minor typos in doc comments (#20623) Update dependency PyYAML to v6.0.3 (#20621) Update cargo-bins/cargo-binstall action to v1.15.6 (#20620) Fixed documentation for try_consider_else (#20587) [ty] Use `Top` materializations for `TypeIs` special form (#20591) [ty] Simplify `Any | (Any & T)` to `Any` (#20593) ...
TypeMappingis no longer cow-shaped.Before,
TypeMappingdefined ato_ownedmethod, which would make an owned copy of the type mapping. This let us apply type mappings to function literals lazily. The primary part of a function that you have to apply the type mapping to is its signature. The hypothesis was that doing this lazily would prevent us from constructing the signature of a function just to apply a type mapping; if you never ended up needed the updated function signature, that would be extraneous work.But looking at the CI for this PR, it looks like that hypothesis is wrong! And this definitely cleans up the code quite a bit. It also means that over time we can consider replacing all of these
TypeMappingenum variants with separateTypeTransformerimpls.