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
63 changes: 54 additions & 9 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,17 +1007,19 @@ def analyze_unbound_type_without_type_info(
# context. This is slightly problematic as it allows using the type 'Any'
# as a base class -- however, this will fail soon at runtime so the problem
# is pretty minor.
#
# Also allow indexing a tuple-typed value that yields Any (or type / Type[Any])
# in base class position, e.g. class C(args[1]) where args: tuple[int, Any].
if isinstance(sym.node, Var):
typ = get_proper_type(sym.node.type)
if isinstance(typ, AnyType):
return AnyType(
TypeOfAny.from_unimported_type, missing_import_name=typ.missing_import_name
)
elif self.allow_type_any:
if isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
return AnyType(TypeOfAny.special_form)
if isinstance(typ, TypeType) and isinstance(typ.item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=typ.item)
runtime_type = self.anal_type_from_runtime_value(typ)
if runtime_type is not None:
return runtime_type
if self.allow_type_any and t.args:
item = self.indexed_tuple_item_type(typ, t.args)
runtime_type = self.anal_type_from_runtime_value(item)
if runtime_type is not None:
return runtime_type
# Option 2:
# Unbound type variable. Currently these may be still valid,
# for example when defining a generic type alias.
Expand Down Expand Up @@ -1121,6 +1123,49 @@ def analyze_unbound_type_without_type_info(
# see https://github.com/python/mypy/issues/4987.
return t

def anal_type_from_runtime_value(self, typ: ProperType | None) -> Type | None:
"""Return a type if a runtime value of this type may be used as a type.

This is used for dynamic base classes: a variable of type Any, type, or
Type[Any] is a valid base. Returns None if the value is not valid as a type.
"""
if isinstance(typ, AnyType):
return AnyType(
TypeOfAny.from_unimported_type, missing_import_name=typ.missing_import_name
)
if self.allow_type_any:
if isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
return AnyType(TypeOfAny.special_form)
if isinstance(typ, TypeType) and isinstance(typ.item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=typ.item)
return None

def indexed_tuple_item_type(
self, typ: ProperType | None, args: tuple[Type, ...]
) -> ProperType | None:
"""If this looks like indexing a tuple-typed value, return the item type."""
if typ is None or len(args) != 1:
return None
if isinstance(typ, Instance) and typ.type.fullname in TUPLE_NAMES and typ.args:
# Homogeneous tuple[T, ...]: any index has type T.
return get_proper_type(typ.args[0])
if isinstance(typ, TupleType):
items = flatten_nested_tuples(typ.items)
if find_unpack_in_list(items) is not None:
return None
index = args[0]
if not isinstance(index, RawExpressionType) or not isinstance(
index.literal_value, int
):
return None
i = index.literal_value
n = len(items)
if i < 0:
i += n
if 0 <= i < n:
return get_proper_type(items[i])
return None

def visit_any(self, t: AnyType) -> Type:
return t

Expand Down
85 changes: 85 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -9727,3 +9727,88 @@ def f() -> None:
class X:
...
undefined # E: Name "undefined" is not defined

[case testSubclassAnyFromIndexedTuple]
# https://github.com/python/mypy/issues/21998
from typing import Any, Tuple

def from_tuple(t: Tuple[int, Any]) -> None:
class Sub(t[1]):
pass

def from_star_any(*args: Any) -> None:
class Sub(args[0]):
pass

def from_any_param(base: Any) -> None:
class Sub(base):
pass

def from_annotated_index(*args: Any) -> None:
x: Any = args[0]
class Sub(x):
pass
[builtins fixtures/tuple.pyi]

[case testSubclassAnyFromUnpackedStarArgs]
# https://github.com/python/mypy/issues/21998
# flags: --python-version 3.12
from typing import Any

def from_unpacked(*args: *tuple[int, Any]) -> None:
class Sub(args[1]):
pass

def from_unpacked_type_annotation(*args: *tuple[int, Any]) -> None:
x: type = args[1]
class Sub(x):
pass
[builtins fixtures/tuple.pyi]

[case testSubclassAnyFromNestedUnpackedStarArgs]
# https://github.com/python/mypy/issues/21998
# flags: --python-version 3.12
from typing import Any

def from_unpacked(*args: *tuple[int, *tuple[Any]]) -> None:
reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
class Sub(args[1]):
pass

def from_unpacked_type_annotation(*args: *tuple[int, *tuple[Any]]) -> None:
reveal_type(args) # N: Revealed type is "tuple[builtins.int, Any]"
x: type = args[1]
class Sub(x):
pass
[builtins fixtures/tuple.pyi]

[case testSubclassIndexedNonAnyStillInvalid]
# https://github.com/python/mypy/issues/21998
from typing import Any, Tuple

def from_str_tuple(t: Tuple[int, str]) -> None:
class Sub(t[1]): pass # E: Variable "t" is not valid as a type \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \
# E: Invalid base class "t" \
# E: Invalid type: try using Literal[1] instead?

def from_int_index(t: Tuple[int, Any]) -> None:
class Sub(t[0]): pass # E: Variable "t" is not valid as a type \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \
# E: Invalid base class "t" \
# E: Invalid type: try using Literal[0] instead?

def from_int_star(*args: int) -> None:
class Sub(args[0]): pass # E: Variable "args" is not valid as a type \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \
# E: Invalid base class "args" \
# E: Invalid type: try using Literal[0] instead?

def unannotated_local(base: Any) -> None:
# Class bases are resolved before type inference, so an unannotated
# local still needs an explicit Any/type annotation.
x = base
class Sub(x): pass # E: Variable "x" is not valid as a type \
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \
# E: Invalid base class "x"
[builtins fixtures/tuple.pyi]
Loading