[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

@AlexWaygood

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.

@AlexWaygood

@github-actions

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

@github-actions

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@AlexWaygood

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!

@dcreager

dcreager

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 ✔️

@MichaReiser

@AlexWaygood

AlexWaygood

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

dcreager

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

ty

Multi-file analysis & type inference