Fix reassignment of generic TypeAliasType treated as type expression
Fixes #20308
Summary
This PR fixes a false positive [type-arg] error when reassigning a generic PEP 695 TypeAliasType to a variable without type arguments.
The Fix
Modified is_type_ref in semanal.py to return False when the target is a generic TypeAliasType used without subscripts. This forces the analyzer to treat it as a variable assignment rather than a type definition.
Verification I verified this locally with a reproduction script.
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
Would you mind adding tests for the new behaviour?
test-data/unit/check-python312.testis a good place to add them
@hauntsaninja Thanks for the review.
I added the regression test to check-python312.test, but I am hitting a persistent test fixture error locally: AssertionError: Var(TypeVar).
It seems the test environment defines TypeVar as a Var, but checkexpr.py expects it to be a TypeInfo (class) when analyzing the TypeAliasType assignment.
I tried adding [builtins fixtures/list.pyi] and [builtins fixtures/dict.pyi] to the test case to load full definitions, but the assertion failure persists.
Could you advise on which fixture is required for testing PEP 695 TypeAliasType assignments?
Here is the test case I am adding:
[[case testPep695GenericTypeAliasReassignment]
from typing import reveal_type
type A[T] = T | str
B = A
reveal_type(B)
[out]
main:5: note: Revealed type is "typing.TypeAliasType"
[builtins fixtures/tuple.pyi]
Would you mind adding tests for the new behaviour?
test-data/unit/check-python312.testis a good place to add them@hauntsaninja Thanks for the review.
I added the regression test to check-python312.test, but I am hitting a persistent test fixture error locally: AssertionError: Var(TypeVar).
It seems the test environment defines TypeVar as a Var, but checkexpr.py expects it to be a TypeInfo (class) when analyzing the TypeAliasType assignment.
I tried adding [builtins fixtures/list.pyi] and [builtins fixtures/dict.pyi] to the test case to load full definitions, but the assertion failure persists.
Could you advise on which fixture is required for testing PEP 695 TypeAliasType assignments?
Here is the test case I am adding:
[[case testPep695GenericTypeAliasReassignment] from typing import reveal_type type A[T] = T | str B = A reveal_type(B) [out] main:5: note: Revealed type is "typing.TypeAliasType" [builtins fixtures/tuple.pyi]
Quick update: I managed to resolve the fixture issue mentioned above.
I found that explicitly adding [typing fixtures/typing-full.pyi] was required to ensure TypeVar is loaded as a TypeInfo (class) rather than a Var in the test environment. The regression test is now added and passing locally.
Please let me know if any further changes are required. Thanks for your kind attention to this matter!
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅
Nice, thanks for adding the test cases and fighting the fixtures (they're the most annoying part about developing mypy).
Hmm, looking at your test case I think this prevents the use of
Bin type annotations entirely. That is,def foo(x: B) -> None: ...will now emit an errorVariable "B" is not valid as a type. I'm guessing that's not what @jorenham expectsMaybe there is a solution that involves looking at whether the right hand side is a Python 3.12 type alias in typeanal.py, maybe somewhere around instantiate_type_alias
@hauntsaninja Thank you again for your patience and detailed guidance. I want to confirm the final implementation strategy before pushing:
I will revert the logic in semanal.py back to its original state.
The fix will be implemented inside instantiate_type_alias in typeanal.py. We will add a check there that, if a generic PEP 695 alias is used with zero arguments (act_len == 0), the function immediately returns the raw TypeAliasType(node, []) object, bypassing the argument count error.
My understanding is that this solves the original assignment issue while preserving B as a valid generic type for later use (e.g., x: B[int]).
Does this core strategy align with your expectations?
Diff from mypy_primer, showing the effect of this PR on open source code:
scipy-stubs (https://github.com/scipy/scipy-stubs)
+ scipy-stubs/io/_fortran.pyi:38: error: Overloaded function signatures 2 and 3 overlap with incompatible return types [overload-overlap]
+ scipy-stubs/io/_fortran.pyi:38: note: Flipping the order of overloads will fix this error
+ scipy-stubs/io/_fortran.pyi:44: error: Overloaded function signatures 2 and 3 overlap with incompatible return types [overload-overlap]
+ scipy-stubs/io/_fortran.pyi:44: note: Flipping the order of overloads will fix this error
+ tests/datasets/test_utils.pyi:10: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int, int], dtype[unsignedinteger[_8Bit]]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:15: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int], dtype[float64]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:20: error: List item 0 has incompatible type overloaded function; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int, int], dtype[unsignedinteger[_8Bit]]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 1 has incompatible type "Callable[[], ndarray[tuple[int], dtype[float64]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 2 has incompatible type overloaded function; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
Diff from mypy_primer, showing the effect of this PR on open source code:
scipy-stubs (https://github.com/scipy/scipy-stubs)
+ scipy-stubs/io/_fortran.pyi:38: error: Overloaded function signatures 2 and 3 overlap with incompatible return types [overload-overlap]
+ scipy-stubs/io/_fortran.pyi:38: note: Flipping the order of overloads will fix this error
+ scipy-stubs/io/_fortran.pyi:44: error: Overloaded function signatures 2 and 3 overlap with incompatible return types [overload-overlap]
+ scipy-stubs/io/_fortran.pyi:44: note: Flipping the order of overloads will fix this error
+ tests/datasets/test_utils.pyi:10: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int, int], dtype[unsignedinteger[_8Bit]]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:15: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int], dtype[float64]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:20: error: List item 0 has incompatible type overloaded function; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 0 has incompatible type "Callable[[], ndarray[tuple[int, int], dtype[unsignedinteger[_8Bit]]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 1 has incompatible type "Callable[[], ndarray[tuple[int], dtype[float64]]]"; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]
+ tests/datasets/test_utils.pyi:24: error: List item 2 has incompatible type overloaded function; expected "Callable[[], ndarray[_NDT, dtype[_SCT]]]" [list-item]