Skip to content

Conversation

AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Jun 27, 2025

Summary

Ensure that we correctly infer calls such as tuple((1, 2)), tuple(range(42)), etc. Ensure that we emit errors on invalid calls such as tuple[int, str]().

Test Plan

Mdtests

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Jun 27, 2025
Copy link
Contributor

github-actions bot commented Jun 27, 2025

mypy_primer results

Changes were detected when running on open source projects
attrs (https://github.com/python-attrs/attrs)
- error[invalid-assignment] src/attr/_make.py:1559:5: Object of type `tuple[Unknown, ...]` is not assignable to `list[Unknown]`
+ error[invalid-assignment] src/attr/_make.py:1559:5: Object of type `tuple[@Todo(generator type), ...]` is not assignable to `list[Unknown]`

vision (https://github.com/pytorch/vision)
- error[invalid-assignment] references/classification/utils.py:420:5: Object of type `tuple[Unknown, ...]` is not assignable to `list[type] | None`
+ error[invalid-assignment] references/classification/utils.py:420:5: Object of type `tuple[@Todo(map_with_boundness: intersections with negative contributions), ...]` is not assignable to `list[type] | None`
- error[invalid-argument-type] torchvision/ops/poolers.py:283:34: Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `tuple[int] | list[int] | int`
+ error[invalid-argument-type] torchvision/ops/poolers.py:283:34: Argument to class `tuple` is incorrect: Expected `Iterable[object]`, found `tuple[int] | list[int] | int`
- error[invalid-argument-type] torchvision/transforms/_functional_pil.py:179:42: Argument to function `expand` is incorrect: Expected `int | tuple[int, ...]`, found `(int & Number) | (list[int] & Number & ~list[Unknown]) | (tuple[int, ...] & Number) | (tuple[int, ...] & tuple[Unknown, ...]) | (list[int] & list[Unknown] & ~list[Unknown]) | tuple[Unknown, ...] | @Todo(Subscript expressions on intersections)`
+ error[invalid-argument-type] torchvision/transforms/_functional_pil.py:179:42: Argument to function `expand` is incorrect: Expected `int | tuple[int, ...]`, found `(int & Number) | (list[int] & Number & ~list[Unknown]) | (tuple[int, ...] & Number) | (tuple[int, ...] & tuple[Unknown, ...]) | (list[int] & list[Unknown] & ~list[Unknown]) | @Todo(Subscript expressions on intersections)`
- error[invalid-argument-type] torchvision/transforms/_functional_pil.py:183:37: Argument to function `expand` is incorrect: Expected `int | tuple[int, ...]`, found `(int & Number) | (list[int] & Number & ~list[Unknown]) | (tuple[int, ...] & Number) | (tuple[int, ...] & tuple[Unknown, ...]) | (list[int] & list[Unknown] & ~list[Unknown]) | tuple[Unknown, ...] | @Todo(Subscript expressions on intersections)`
+ error[invalid-argument-type] torchvision/transforms/_functional_pil.py:183:37: Argument to function `expand` is incorrect: Expected `int | tuple[int, ...]`, found `(int & Number) | (list[int] & Number & ~list[Unknown]) | (tuple[int, ...] & Number) | (tuple[int, ...] & tuple[Unknown, ...]) | (list[int] & list[Unknown] & ~list[Unknown]) | @Todo(Subscript expressions on intersections)`

pywin32 (https://github.com/mhammond/pywin32)
- error[invalid-argument-type] win32/Demos/security/setkernelobjectsecurity.py:77:12: Argument to function `AdjustTokenPrivileges` is incorrect: Expected `PyTOKEN_PRIVILEGES`, found `tuple[Unknown, ...]`
+ error[invalid-argument-type] win32/Demos/security/setkernelobjectsecurity.py:77:12: Argument to function `AdjustTokenPrivileges` is incorrect: Expected `PyTOKEN_PRIVILEGES`, found `tuple[@Todo(generator type), ...]`

static-frame (https://github.com/static-frame/static-frame)
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4597:56: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4600:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4609:56: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4612:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4624:53: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4627:69: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4640:53: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4643:56: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4646:71: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4659:65: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4662:65: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4665:55: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4678:65: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4681:65: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4684:71: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4700:53: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4703:55: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4711:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4714:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4717:63: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4729:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4732:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4735:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4747:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4750:59: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4753:63: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4764:67: Unused blanket `type: ignore` directive
- warning[unused-ignore-comment] static_frame/test/unit/test_series.py:4767:68: Unused blanket `type: ignore` directive
- Found 1839 diagnostics
+ Found 1811 diagnostics

pytest (https://github.com/pytest-dev/pytest)
- error[invalid-argument-type] src/_pytest/fixtures.py:1387:70: Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `(Sequence[object] & ~((...) -> object)) | (((Any, /) -> object) & ~((...) -> object))`
+ error[invalid-argument-type] src/_pytest/fixtures.py:1387:70: Argument to class `tuple` is incorrect: Expected `Iterable[object]`, found `(Sequence[object] & ~((...) -> object)) | (((Any, /) -> object) & ~((...) -> object))`

sympy (https://github.com/sympy/sympy)
- error[invalid-argument-type] sympy/combinatorics/fp_groups.py:25:33: Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `(bound method FpGroup._generators() -> Unknown) | @Todo(instance attribute on class with dynamic base)`
+ error[invalid-argument-type] sympy/combinatorics/fp_groups.py:25:33: Argument to class `tuple` is incorrect: Expected `Iterable[object]`, found `(bound method FpGroup._generators() -> Unknown) | @Todo(instance attribute on class with dynamic base)`
- error[invalid-argument-type] sympy/stats/joint_rv_types.py:192:29: Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Unknown | ImmutableDenseMatrix`
+ error[invalid-argument-type] sympy/stats/joint_rv_types.py:192:29: Argument to class `tuple` is incorrect: Expected `Iterable[object]`, found `Unknown | ImmutableDenseMatrix`

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Jun 27, 2025

The new errors on static-frame are because ndarray has a to_list() method but Series does not, and post[0][1] here is now inferred as ndarray | Series where it was previously inferred as Unknown. Other type checkers have the same inference as us here, which is why all these calls already have type: ignores on them, and therefore why these errors present as "unused type: ignore" errors going away.

@AlexWaygood AlexWaygood marked this pull request as ready for review June 27, 2025 18:20
@AlexWaygood AlexWaygood merged commit a50a993 into main Jun 27, 2025
36 checks passed
@AlexWaygood AlexWaygood deleted the alex/sound-tuples branch June 27, 2025 18:37
@@ -2426,6 +2427,7 @@ impl KnownClass {
| Self::Float
| Self::Enum
| Self::ABCMeta
| KnownClass::Iterable
Copy link
Member

Choose a reason for hiding this comment

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

nit: Self::Iterable to be consistent with the other arms

Some(KnownClass::Tuple) if overload_index == 1 => {
if let [Some(argument)] = overload.parameter_types() {
let overridden_return =
argument.into_tuple().map(Type::Tuple).unwrap_or_else(|| {
Copy link
Member

Choose a reason for hiding this comment

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

I think this would avoid unwrapping the argument type just to then rewrap it:

Suggested change
argument.into_tuple().map(Type::Tuple).unwrap_or_else(|| {
argument.filter(Type::is_tuple).unwrap_or_else(|| {

(though it depends on an is_tuple method that may not exist yet!)

Copy link
Member Author

Choose a reason for hiding this comment

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

argument is a type rather than an Option here so I think this would have to be

Suggested change
argument.into_tuple().map(Type::Tuple).unwrap_or_else(|| {
argument.into_tuple().filter(Type::is_tuple).unwrap_or_else(|| {

Adding a Type::is_tuple method just for that feels a bit unnecessary to me? No strong opinion though

Type::GenericAlias(alias) => {
let instantiated = Type::instance(db, ClassType::from(alias));

let parameters = if alias.origin(db).is_known(db, KnownClass::Tuple) {
Copy link
Member

Choose a reason for hiding this comment

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

I think I follow the constructors here, but can you put a comment with the Python syntax of the signature you're trying to construct here?

let spec = alias.specialization(db).tuple(db);
let mut parameter =
Parameter::positional_only(Some(Name::new_static("iterable")))
.with_annotated_type(instantiated);
Copy link
Member

Choose a reason for hiding this comment

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

With the Tuple::resize method that #18948 introduces, we can make this work for any tuple, not just one with exactly the same size as the type being constructed. I also have plans to have try_iterator return a tuple spec describing the return values of the iterator, which would then give us what we need for this to work for any iterable type.

Copy link
Member Author

Choose a reason for hiding this comment

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

With the Tuple::resize method that #18948 introduces, we can make this work for any tuple, not just one with exactly the same size as the type being constructed.

Hmm, not sure I totally follow this. Can you give an example of something that should be covered in this PR but isn't? :-)

Copy link
Member

@dcreager dcreager Jun 27, 2025

Choose a reason for hiding this comment

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

Sorry, wasn't requesting any changes here with this comment, just leaving me/others a breadcrumb for a future improvement

dcreager added a commit that referenced this pull request Jun 27, 2025
* main:
  [ty] Make tuple instantiations sound (#18987)
  [`flake8-pyi`] Expand `Optional[A]` to `A | None` (`PYI016`) (#18572)
  Convert `OldDiagnostic::noqa_code` to an `Option<String>` (#18946)
  [ty] Fix playground (#18986)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants