[ty] Upcast heterogeneous and mixed tuples to homogeneous tuples where it's necessary to solve a TypeVar by AlexWaygood · Pull Request #19635 · astral-sh/ruff (original) (raw)
Conversation
Summary
This PR improves our generics solver such that we are able to solve the TypeVar in this snippet to int | str (the union of the elements in the heterogeneous tuple) by upcasting the heterogeneous tuple to its pure-homogeneous-tuple supertype:
def f[T](x: tuple[T, ...]) -> T: return x[0]
def g(x: tuple[int, str]): reveal_type(f(x))
Test Plan
Mdtests. Some TODOs remain in the mdtest regarding solving TypeVars for mixed tuples, but I think this PR on its own is a significant step forward for our generics solver when it comes to tuple types.
Diagnostic diff on typing conformance tests
Changes were detected when running ty on typing conformance tests
--- old-output.txt 2025-07-30 14:41:54.734946858 +0000
+++ new-output.txt 2025-07-30 14:41:54.794947483 +0000
@@ -715,7 +715,6 @@
narrowing_typeguard.py:85:9: error[type-assertion-failure] Argument does not have asserted type int
narrowing_typeguard.py:89:9: error[type-assertion-failure] Argument does not have asserted type B
narrowing_typeguard.py:93:9: error[type-assertion-failure] Argument does not have asserted type B
-narrowing_typeis.py:19:9: error[type-assertion-failure] Argument does not have asserted type tuple[str, str]
narrowing_typeis.py:21:9: error[type-assertion-failure] Argument does not have asserted type tuple[str, ...]
narrowing_typeis.py:38:9: error[type-assertion-failure] Argument does not have asserted type int
narrowing_typeis.py:72:9: error[type-assertion-failure] Argument does not have asserted type int
@@ -889,4 +888,4 @@
tuples_type_form.py:36:1: error[invalid-assignment] Object of type tuple[Literal[1], Literal[2], Literal[3], Literal[""]] is not assignable to tuple[int, ...]
typeddicts_operations.py:60:1: error[type-assertion-failure] Argument does not have asserted type str | None
typeddicts_type_consistency.py:101:1: error[invalid-assignment] Object of type Unknown | None is not assignable to str
-Found 890 diagnostics
+Found 889 diagnostics
mypy_primer results
No ecosystem changes detected ✅
No memory usage changes detected ✅
I expected some primer report here, but I guess we just inferred Unknown when solving (well, failing to solve) these TypeVars before, so it wasn't a significant source of false positives 😄
The change on the typing conformance tests anyway looks correct!
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed up a commit that simplifies the tuple inference logic by using TupleSpec::resize, so this probably deserves 👀 from someone other than me in addition to my ✔️
Comment on lines +764 to +766
| let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else { |
|---|
| return Ok(()); |
| }; |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcreager is it correct to return Ok(()) here or should I propagate the error by returning a SpecializationError? On this snippet we have this behaviour, which seems reasonable?
def f[T, U](t: tuple[T, U]) -> T: ...
def _(arg: tuple[int, str, bool]):
# revealed: Unknown
reveal_type(f(arg)) # Argument to function f is incorrect: Expected tuple[Unknown, Unknown], found tuple[int, str, bool] (invalid-argument-type)
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this example is showing that the length mismatch will be caught during type checking, so I don't think we need to raise it as a specialization error here
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 to the most_precise changes you pushed up
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
[ Show hidden characters]({{ revealButtonHref }})
Labels
Multi-file analysis & type inference