Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2658,7 +2658,7 @@ def check_typevar_defaults(self, tvars: Sequence[TypeVarLikeType]) -> None:
continue
if not is_subtype(tv.default, tv.upper_bound):
self.fail("TypeVar default must be a subtype of the bound type", tv)
if tv.values and not any(tv.default == value for value in tv.values):
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
Copy link

Choose a reason for hiding this comment

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

Type Comparison Issue

Using equality (==) instead of is_same_type() for type comparison can lead to incorrect constraint validation. This may cause TypeVar defaults that are semantically equivalent but not identical to be incorrectly rejected, reducing type system reliability.

            if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
Commitable Suggestion
Suggested change
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
Standards
  • ISO-IEC-25010-Functional-Correctness
  • ISO-IEC-25010-Reliability-Maturity

Copy link

Choose a reason for hiding this comment

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

Type Comparison Logic

The change from equality check to is_same_type() affects type comparison behavior. While this fixes the immediate issue, it may have unintended consequences for other type comparisons that relied on exact equality. This could lead to incorrect type validation in other scenarios.

            # Use is_same_type for structural equivalence of types including aliases
            if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
Commitable Suggestion
Suggested change
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
# Use is_same_type for structural equivalence of types including aliases
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
Standards
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • ISO-IEC-25010-Reliability-Maturity

Copy link

Choose a reason for hiding this comment

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

Duplicate Equality Check

The equality check was changed from '==' to 'is_same_type()' function. This same change was also made in checkexpr.py, suggesting a common operation that could be extracted to avoid duplication and ensure consistent behavior across the codebase.

Standards
  • Clean-Code-DRY
  • Maintainability-Quality-Consistency
  • Refactoring-Extract-Method

self.fail("TypeVar default must be one of the constraint types", tv)

def check_enum(self, defn: ClassDef) -> None:
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6210,7 +6210,7 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
):
if not is_subtype(p_default, e.upper_bound):
self.chk.fail("TypeVar default must be a subtype of the bound type", e)
if e.values and not any(p_default == value for value in e.values):
if e.values and not any(is_same_type(p_default, value) for value in e.values):
Copy link

Choose a reason for hiding this comment

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

Inconsistent Type Comparison

Using equality (==) instead of is_same_type() for type comparison in TypeVar expression validation. This creates inconsistent behavior between checker.py and checkexpr.py, potentially causing different validation results for the same types.

            if e.values and not any(is_same_type(p_default, value) for value in e.values):
Commitable Suggestion
Suggested change
if e.values and not any(is_same_type(p_default, value) for value in e.values):
if e.values and not any(is_same_type(p_default, value) for value in e.values):
Standards
  • ISO-IEC-25010-Functional-Correctness
  • ISO-IEC-25010-Reliability-Maturity

Copy link

Choose a reason for hiding this comment

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

Inconsistent Type Comparison

The same type comparison logic change was made in checkexpr.py, creating a parallel modification. If these functions are used in different contexts, inconsistent behavior could occur if one is updated without the other in future changes, leading to reliability issues.

            # Use is_same_type for structural equivalence of types including aliases
            if e.values and not any(is_same_type(p_default, value) for value in e.values):
Commitable Suggestion
Suggested change
if e.values and not any(is_same_type(p_default, value) for value in e.values):
# Use is_same_type for structural equivalence of types including aliases
if e.values and not any(is_same_type(p_default, value) for value in e.values):
Standards
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • ISO-IEC-25010-Reliability-Maturity

self.chk.fail("TypeVar default must be one of the constraint types", e)
return AnyType(TypeOfAny.special_form)

Expand Down
24 changes: 24 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -2042,3 +2042,27 @@ tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type i
b: tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testPEP695TypeVarConstraintsDefaultAliases]
from typing import Generic
from typing_extensions import TypeVar

type K = int
type V = int
type L = list[int]

T1 = TypeVar("T1", str, K, default=K)
T2 = TypeVar("T2", str, K, default=V)
T3 = TypeVar("T3", str, L, default=L)

class A1(Generic[T1]):
x: T1
class A2(Generic[T2]):
x: T2
class A3(Generic[T3]):
x: T3

reveal_type(A1().x) # N: Revealed type is "builtins.int"
reveal_type(A2().x) # N: Revealed type is "builtins.int"
reveal_type(A3().x) # N: Revealed type is "builtins.list[builtins.int]"
[builtins fixtures/tuple.pyi]
16 changes: 16 additions & 0 deletions test-data/unit/check-python313.test
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,19 @@ def func_d1(
reveal_type(d) # N: Revealed type is "__main__.A[builtins.float, builtins.str]"
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testTypeVarConstraintsDefaultAliasesInline]
type K = int
type V = int

class A1[T: (str, int) = K]:
x: T
class A2[T: (str, K) = K]:
x: T
class A3[T: (str, K) = V]:
x: T

reveal_type(A1().x) # N: Revealed type is "builtins.int"
reveal_type(A2().x) # N: Revealed type is "builtins.int"
reveal_type(A3().x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]
103 changes: 101 additions & 2 deletions test-data/unit/check-typevar-defaults.test
Original file line number Diff line number Diff line change
Expand Up @@ -729,8 +729,6 @@ class C(Generic[_I]): pass
t: type[C] | int = C
[builtins fixtures/tuple.pyi]



[case testGenericTypeAliasWithDefaultTypeVarPreservesNoneInDefault]
from typing_extensions import TypeVar
from typing import Generic, Union
Expand All @@ -749,3 +747,104 @@ MyA = A[T1, int]
a: MyA = A(None, 10)
reveal_type(a.a) # N: Revealed type is "Union[builtins.int, None]"
[builtins fixtures/tuple.pyi]

[case testTypeVarConstraintsDefaultAliasesTypeAliasType]
from typing import Generic
from typing_extensions import TypeAliasType, TypeVar

K = TypeAliasType("K", int)
V = TypeAliasType("V", int)
L = TypeAliasType("L", list[int])
T1 = TypeVar("T1", str, K, default=K)
T2 = TypeVar("T2", str, K, default=V)
T3 = TypeVar("T3", str, L, default=L)

class A1(Generic[T1]):
x: T1
class A2(Generic[T2]):
x: T2
class A3(Generic[T3]):
x: T3

reveal_type(A1().x) # N: Revealed type is "builtins.int"
reveal_type(A2().x) # N: Revealed type is "builtins.int"
reveal_type(A3().x) # N: Revealed type is "builtins.list[builtins.int]"
[builtins fixtures/tuple.pyi]

[case testTypeVarConstraintsDefaultAliasesImplicitAlias]
from typing_extensions import TypeVar

K = int
V = int
L = list[int]
T1 = TypeVar("T1", str, K, default=K)
T2 = TypeVar("T2", str, K, default=V)
T3 = TypeVar("T3", str, L, default=L)
[builtins fixtures/tuple.pyi]

[case testTypeVarConstraintsDefaultAliasesExplicitAlias]
from typing_extensions import TypeAlias, TypeVar

K: TypeAlias = int
V: TypeAlias = int
L: TypeAlias = list[int]
T1 = TypeVar("T1", str, K, default=K)
T2 = TypeVar("T2", str, K, default=V)
T3 = TypeVar("T3", str, L, default=L)
[builtins fixtures/tuple.pyi]

[case testTypeVarConstraintsDefaultSpecialTypes]
from typing import Generic, NamedTuple
from typing_extensions import TypedDict, TypeVar

class TD(TypedDict):
foo: str

class NT(NamedTuple):
foo: str

T1 = TypeVar("T1", str, TD, default=TD)
T2 = TypeVar("T2", str, NT, default=NT)

class A1(Generic[T1]):
x: T1
class A2(Generic[T2]):
x: T2

reveal_type(A1().x) # N: Revealed type is "TypedDict('__main__.TD', {'foo': builtins.str})"
reveal_type(A2().x) # N: Revealed type is "Tuple[builtins.str, fallback=__main__.NT]"
[builtins fixtures/tuple.pyi]

[case testTypeVarConstraintsDefaultSpecialTypesGeneric]
from typing import Generic, NamedTuple
from typing_extensions import TypedDict, TypeVar

T = TypeVar("T")

class TD(TypedDict, Generic[T]):
foo: T
class TD2(TD[int]): pass
class TD3(TD[int]):
bar: str

class NT(NamedTuple, Generic[T]):
foo: T
class NT2(NT[int]): pass

T1 = TypeVar("T1", str, TD[int], default=TD[int])
T2 = TypeVar("T2", str, NT[int], default=NT[int])
T3 = TypeVar("T3", str, TD2, default=TD[int])
T4 = TypeVar("T4", str, TD3, default=TD[int]) # E: TypeVar default must be one of the constraint types
T5 = TypeVar("T5", str, NT2, default=NT[int]) # E: TypeVar default must be one of the constraint types

class A1(Generic[T1]):
x: T1
class A2(Generic[T2]):
x: T2
class A3(Generic[T3]):
x: T3

reveal_type(A1().x) # N: Revealed type is "TypedDict('__main__.TD', {'foo': builtins.int})"
reveal_type(A2().x) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.NT[builtins.int]]"
reveal_type(A3().x) # N: Revealed type is "TypedDict('__main__.TD', {'foo': builtins.int})"
[builtins fixtures/tuple.pyi]