Skip to content

[ty] Allow tuple[Any, ...] to assign to tuple[int, *tuple[int, ...]]#21803

Merged
charliermarsh merged 2 commits intomainfrom
charlie/t
Dec 5, 2025
Merged

[ty] Allow tuple[Any, ...] to assign to tuple[int, *tuple[int, ...]]#21803
charliermarsh merged 2 commits intomainfrom
charlie/t

Conversation

@charliermarsh
Copy link
Member

Summary

Closes astral-sh/ty#1750.

@charliermarsh charliermarsh added the ty Multi-file analysis & type inference label Dec 5, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 5, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-12-05 19:01:00.800057224 +0000
+++ new-output.txt	2025-12-05 19:01:04.523089702 +0000
@@ -934,7 +934,6 @@
 tuples_type_compat.py:32:10: error[invalid-assignment] Object of type `tuple[int, *tuple[int, ...]]` is not assignable to `tuple[int]`
 tuples_type_compat.py:33:10: error[invalid-assignment] Object of type `tuple[int, ...]` is not assignable to `tuple[int]`
 tuples_type_compat.py:43:22: error[invalid-assignment] Object of type `tuple[int, ...]` is not assignable to `tuple[int]`
-tuples_type_compat.py:47:40: error[invalid-assignment] Object of type `tuple[Any, ...]` is not assignable to `tuple[int, *tuple[str, ...]]`
 tuples_type_compat.py:62:26: error[invalid-assignment] Object of type `tuple[int, ...]` is not assignable to `tuple[int, int]`
 tuples_type_compat.py:75:9: error[type-assertion-failure] Type `tuple[int]` does not match asserted type `tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]`
 tuples_type_compat.py:80:9: error[type-assertion-failure] Type `tuple[str, str] | tuple[int, int]` does not match asserted type `tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]`
@@ -1031,4 +1030,4 @@
 typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
 typeddicts_usage.py:28:18: error[invalid-key] Unknown key "title" for TypedDict `Movie`: Unknown key "title"
 typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions
-Found 1033 diagnostics
+Found 1032 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 5, 2025

mypy_primer results

Changes were detected when running on open source projects
beartype (https://github.com/beartype/beartype)
- beartype/claw/_package/clawpkgtrie.py:66:29: warning[unsupported-base] Unsupported class base with type `<class 'dict[str, PackagesTrieBlacklist]'> | <class 'dict[str, Divergent]'>`
- beartype/claw/_package/clawpkgtrie.py:247:29: warning[unsupported-base] Unsupported class base with type `<class 'dict[str, PackagesTrieWhitelist]'> | <class 'dict[str, Divergent]'>`
- Found 494 diagnostics
+ Found 492 diagnostics

scipy-stubs (https://github.com/scipy/scipy-stubs)
+ scipy-stubs/optimize/_constraints.pyi:56:50: warning[unused-ignore-comment] Unused `ty: ignore` directive
+ scipy-stubs/sparse/_base.pyi:120:98: warning[unused-ignore-comment] Unused `ty: ignore` directive
+ scipy-stubs/sparse/_base.pyi:933:108: warning[unused-ignore-comment] Unused `ty: ignore` directive
+ scipy-stubs/sparse/_base.pyi:943:108: warning[unused-ignore-comment] Unused `ty: ignore` directive
+ scipy-stubs/sparse/linalg/_expm_multiply.pyi:22:67: warning[unused-ignore-comment] Unused `ty: ignore` directive
- Found 96 diagnostics
+ Found 101 diagnostics

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
- pandas-stubs/_typing.pyi:1217:16: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- Found 5518 diagnostics
+ Found 5517 diagnostics

pydantic (https://github.com/pydantic/pydantic)
- pydantic/fields.py:943:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:943:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:983:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:983:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1026:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1026:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1066:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1066:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1109:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1109:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1148:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1148:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1188:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1188:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1567:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, Divergent], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`
+ pydantic/fields.py:1567:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`

No memory usage changes detected ✅

@charliermarsh
Copy link
Member Author

The conformance change is correct.

@charliermarsh
Copy link
Member Author

Confused by the change in scikit-build-core and dd-trace-py though.

@carljm
Copy link
Contributor

carljm commented Dec 5, 2025

Those two are known unstable diagnostics currently, not related to this PR.

@charliermarsh
Copy link
Member Author

Oh let's go, thank you.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

This PR regresses some cases. This assertion passes on main, but fails on your PR branch:

from typing import Any
from ty_extensions import static_assert, is_assignable_to

static_assert(not is_assignable_to(tuple[*tuple[Any, ...], Any], tuple[*tuple[Any, ...], Any, Any]))

The spec carves out a very specific special case here for tuple[Any, ...] in particular. The special case does not apply to "mixed" tuples that have Any as their variable-length element but also have suffixes and prefixes

@AlexWaygood
Copy link
Member

Oh hang on, maybe I'm wrong. The bit at the top of the "tuples" section of the spec suggests that this is a very specific special case. But lower down, the spec also says

If an unpacked *tuple[Any, ...] is embedded within another tuple, that portion of the tuple is consistent with any tuple of any length.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Okay, this looks great to me. Thank you!!

The only thing I think would be great to add would be to add the test case that I initially thought this PR was regressing 😆 I.e., let's add this assertion:

from typing import Any
from ty_extensions import static_assert, is_assignable_to

# `*tuple[Any, ...]` can materialize to a tuple of any length as a special case,
# so this passes:
static_assert(is_assignable_to(tuple[*tuple[Any, ...], Any], tuple[*tuple[Any, ...], Any, Any]))

It looks like we already link to the relevant parts of the spec in that mdtest

@charliermarsh charliermarsh enabled auto-merge (squash) December 5, 2025 19:01
@charliermarsh charliermarsh merged commit ef45c97 into main Dec 5, 2025
40 checks passed
@charliermarsh charliermarsh deleted the charlie/t branch December 5, 2025 19:04
dcreager added a commit that referenced this pull request Dec 5, 2025
* origin/main:
  [ty] Allow `tuple[Any, ...]` to assign to `tuple[int, *tuple[int, ...]]` (#21803)
  [ty] Support renaming import aliases (#21792)
  [ty] Add redeclaration LSP tests (#21812)
  [ty] more detailed description of "Size limit on unions of literals" in mdtest (#21804)
  [ty] Complete support for `ParamSpec` (#21445)
  [ty] Update benchmark dependencies (#21815)
dcreager added a commit that referenced this pull request Dec 7, 2025
* origin/main:
  [ty] Add test case for fixed panic (#21832)
  [ty] Avoid double-analyzing tuple in `Final` subscript (#21828)
  [flake8-bandit] Fix false positive when using non-standard `CSafeLoader` path (S506). (#21830)
  Add minimal-size build profile (#21826)
  [ty] Allow `tuple[Any, ...]` to assign to `tuple[int, *tuple[int, ...]]` (#21803)
  [ty] Support renaming import aliases (#21792)
  [ty] Add redeclaration LSP tests (#21812)
  [ty] more detailed description of "Size limit on unions of literals" in mdtest (#21804)
  [ty] Complete support for `ParamSpec` (#21445)
  [ty] Update benchmark dependencies (#21815)
@dhruvmanila dhruvmanila added the bug Something isn't working label Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tuple[Any, ...] not assignable to tuple[int, *tuple[int, ...]]

4 participants

Comments