diff --git a/mypy/checker.py b/mypy/checker.py index 3f2a8476ed93..14d0a69793dc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -89,6 +89,7 @@ def __init__(self) -> None: is_instance_var, ) from mypy.checkpattern import PatternChecker +from mypy.constant_fold import constant_fold_expr from mypy.constraints import SUPERTYPE_OF from mypy.erasetype import ( erase_type, @@ -207,6 +208,7 @@ def __init__(self) -> None: WithStmt, YieldExpr, get_func_def, + get_member_expr_fullname, is_final_node, ) from mypy.operators import flip_ops, int_op_to_method, neg_ops @@ -220,6 +222,7 @@ def __init__(self) -> None: from mypy.semanal_shared import SemanticAnalyzerCoreInterface from mypy.sharedparse import BINARY_MAGIC_METHODS from mypy.state import state +from mypy.stats import get_original_any from mypy.subtypes import ( find_member, has_any_type, @@ -266,6 +269,7 @@ def __init__(self) -> None: MYPYC_NATIVE_INT_NAMES, NOT_IMPLEMENTED_TYPE_NAMES, OVERLOAD_NAMES, + TUPLE_NAMES, AnyType, BoolTypeQuery, CallableType, @@ -296,6 +300,7 @@ def __init__(self) -> None: UnpackType, extend_args_for_prefix_and_suffix, find_unpack_in_list, + flatten_nested_tuples, flatten_nested_unions, get_proper_type, get_proper_types, @@ -2839,9 +2844,253 @@ def check__exit__return_type(self, defn: FuncItem) -> None: ): self.msg.incorrect__exit__return(defn) + def is_explicit_any(self, typ: ProperType) -> bool: + """Is this an Any type that comes from an explicit annotation? + + Unlike ``Any`` that leaks in from unannotated (dynamically typed) code, + an explicitly written ``Any`` means the user opted into dynamic typing, + so it is treated like an explicitly ``Any``-typed variable. + """ + if not isinstance(typ, AnyType): + return False + # Unwrap Any types that come from another Any to get the original. + typ = get_original_any(typ) + # Only accept explicitly written Any, not Any from unannotated code + # or other sources. + return typ.type_of_any == TypeOfAny.explicit + + def check_deferred_base_classes(self, defn: ClassDef) -> None: + """Validate base classes deferred from semantic analysis. + + A base class that is a variable with an inferred (not declared) type -- + e.g. ``x = args[1]`` where ``args[1]`` is ``Any`` -- could not be + validated during semantic analysis, since the type is only inferred by + the type checker. Such bases were provisionally treated as ``Any``; + now that the type is known, accept it if it is ``Any`` (like an + explicitly ``Any``-typed variable) and otherwise report the errors that + semantic analysis deferred. + + A base class may also subscript a variable directly, e.g. + ``class Sub(args[1])``. Then the item type at the subscript is what + gets validated, in the same way. + """ + if not self.in_checked_function(): + # Semantic analysis silently drops these errors in unchecked + # functions; do the same here to avoid new reports. + return + for deferred in defn.info.deferred_base_classes: + var = deferred.var + base_expr = deferred.expr + defining_literal = deferred.defining_literal + if defining_literal: + # Semantic analysis does not report "not valid as a type" for + # variables inside Literal[...]; do the same here. + continue + if isinstance(base_expr, IndexExpr): + typ = self.deferred_index_base_type(var, base_expr) + else: + var_type = var.type + typ = get_proper_type(var_type) if var_type is not None else None + # Mirror the cases semantic analysis accepts for an explicitly + # typed variable (see TypeAnalyser.analyze_unbound_type_without_type_info). + # An inferred plain ``Any`` is only accepted if it comes from an + # explicit annotation; ``Any`` leaking in from unannotated code is + # still rejected, as before. + is_any = typ is not None and self.is_valid_deferred_base_type(typ) + if is_any: + if self.options.disallow_subclassing_any: + if isinstance(base_expr, (NameExpr, MemberExpr)): + msg = f'Class cannot subclass "{base_expr.name}" (has type "Any")' + else: + msg = 'Class cannot subclass value of type "Any"' + self.fail(msg, base_expr) + continue + if isinstance(base_expr, NameExpr): + name_repr: str | None = base_expr.name + elif isinstance(base_expr, MemberExpr): + name_repr = get_member_expr_fullname(base_expr) + elif isinstance(base_expr, IndexExpr): + name_repr = var.name + else: + name_repr = None + self.fail( + f'Variable "{var.fullname}" is not valid as a type', + base_expr, + code=codes.VALID_TYPE, + ) + self.note( + "See https://mypy.readthedocs.io/en/stable/common_issues.html" + "#variables-vs-type-aliases", + base_expr, + code=codes.VALID_TYPE, + ) + msg = "Invalid base class" + if name_repr: + msg += f' "{name_repr}"' + self.fail(msg, base_expr) + if isinstance(base_expr, IndexExpr): + index_value = self.index_expr_int_value(base_expr.index) + if index_value is not None: + # Mirror the hint semantic analysis reports for an + # integer used as a type argument. + self.fail( + f"Invalid type: try using Literal[{index_value}] instead?", + base_expr, + code=codes.VALID_TYPE, + ) + + def is_valid_deferred_base_type(self, typ: ProperType) -> bool: + """Can a deferred base class have this inferred type? + + Mirror what semantic analysis accepts for an explicitly typed + variable: ``Any`` from an explicit annotation, a type object, or a + union of those (e.g. ``type | Any``, when an ambiguous tuple index + may land on either). Anything else is rejected. + """ + if isinstance(typ, UnionType): + return all( + self.is_valid_deferred_base_type(get_proper_type(item)) for item in typ.items + ) + return ( + self.is_explicit_any(typ) + or (isinstance(typ, Instance) and typ.type.fullname == "builtins.type") + or (isinstance(typ, TypeType) and self.is_explicit_any(typ.item)) + ) + + @staticmethod + def index_expr_int_value(index: Expression) -> int | None: + """Value of a constant integer index expression, or None.""" + value = constant_fold_expr(index, "") + if isinstance(value, int) and not isinstance(value, bool): + return value + return None + + @staticmethod + def index_expr_str_value(index: Expression) -> str | None: + """Value of a constant string index expression, or None.""" + value = constant_fold_expr(index, "") + if isinstance(value, str): + return value + return None + + def deferred_index_base_type(self, var: Var, base_expr: IndexExpr) -> ProperType | None: + """Type of a deferred subscripted base class, e.g. ``args[1]``. + + ``var`` is the subscripted variable. Return the type of the item at + the subscript, or None if it cannot be determined statically; the + base class is then rejected, as it was before deferred validation. + Only a constant index is supported: an integer index into a tuple + or sequence type, or a string index into a TypedDict or mapping + type (e.g. ``kwargs['some_any_field']`` for ``**kwargs``), mirroring + what can be known about the item type. + A subscript may itself be subscripted, e.g. ``args[1][0]``; then the + item type is computed inside out, one index at a time. + """ + if isinstance(base_expr.base, NameExpr): + var_type = get_proper_type(var.type) if var.type is not None else None + elif isinstance(base_expr.base, IndexExpr): + var_type = self.deferred_index_base_type(var, base_expr.base) + else: + return None + if var_type is None: + return None + index_value = self.index_expr_int_value(base_expr.index) + if index_value is not None: + if isinstance(var_type, TupleType): + return self.tuple_item_at_index(var_type, index_value) + if isinstance(var_type, Instance): + if var_type.type.fullname in TUPLE_NAMES and var_type.args: + # Homogeneous tuple[T, ...]: every item has type T. + return get_proper_type(var_type.args[0]) + sequence_info = var_type.type.get_base("typing.Sequence") + if sequence_info is not None: + mapped = map_instance_to_supertype(var_type, sequence_info) + if mapped.args: + return get_proper_type(mapped.args[0]) + return None + key = self.index_expr_str_value(base_expr.index) + if key is not None: + if isinstance(var_type, TypedDictType): + item_type = var_type.items.get(key) + if item_type is not None: + return get_proper_type(item_type) + return None + if isinstance(var_type, Instance): + # Look the base up in the MRO directly: get_base() asserts + # if it is missing, which can happen with partial stubs. + mapping_info = next( + (base for base in var_type.type.mro if base.fullname == "typing.Mapping"), None + ) + if mapping_info is not None: + mapped = map_instance_to_supertype(var_type, mapping_info) + if len(mapped.args) >= 2: + return get_proper_type(mapped.args[1]) + return None + return None + + def tuple_item_at_index(self, tuple_type: TupleType, index: int) -> ProperType | None: + """Type of a tuple item at a constant index, or None if ambiguous. + + If the tuple type has a variadic (unpacked) item, an index may land + on more than one item. Then the union of the items it may land on + is returned, and the caller decides whether that union is an + acceptable base class type. A single fixed item (or ``Any``, if + every candidate is ``Any``) is returned directly. + """ + items = [get_proper_type(item) for item in flatten_nested_tuples(tuple_type.items)] + unpack_index = find_unpack_in_list(items) + if unpack_index is None: + pos = index if index >= 0 else index + len(items) + if 0 <= pos < len(items): + return items[pos] + return None + unpack_item = items[unpack_index] + assert isinstance(unpack_item, UnpackType) + unpacked = get_proper_type(unpack_item.type) + if not ( + isinstance(unpacked, Instance) + and unpacked.type.fullname in TUPLE_NAMES + and unpacked.args + ): + return None + variadic_item = get_proper_type(unpacked.args[0]) + prefix = items[:unpack_index] + suffix = items[unpack_index + 1 :] + possible: list[ProperType] + if index >= 0: + if index < len(prefix): + possible = [prefix[index]] + else: + possible = [variadic_item] + # A suffix item sits at position len(prefix) + + k, and the variadic length is unknown, so it may + # also land on this index. + for k, item in enumerate(suffix): + if len(prefix) + k <= index: + possible.append(item) + else: + from_end = -index + if from_end <= len(suffix): + possible = [suffix[len(suffix) - from_end]] + else: + possible = [variadic_item] + # A prefix item sits at distance len(suffix) + + (len(prefix) - k) from the end. + for k, item in enumerate(prefix): + if len(suffix) + (len(prefix) - k) <= from_end: + possible.append(item) + if all(isinstance(item, AnyType) for item in possible): + return possible[0] + if len(possible) == 1: + return possible[0] + # The index may land on several items; return their union and let + # the caller decide whether it is an acceptable base class type. + return make_simplified_union(possible) + def visit_class_def(self, defn: ClassDef) -> None: """Type check a class definition.""" typ = defn.info + self.check_deferred_base_classes(defn) for base in typ.mro[1:]: if base.is_final: self.fail(message_registry.CANNOT_INHERIT_FROM_FINAL.format(base.name), defn) diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index 8c42d205d568..d8920479dc87 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -26,6 +26,7 @@ SymbolTableNode, TupleExpr, UnaryExpr, + Var, get_member_expr_fullname, ) from mypy.options import Options @@ -105,7 +106,40 @@ def expr_to_unanalyzed_type( ) if isinstance(base, UnboundType): if base.args: - raise TypeTranslationError() + # A variable subscripted more than once, e.g. `args[1][0]` + # used as a base class. Flatten the index chain into a + # single UnboundType, so the type analyser can defer it + # like a single subscript. Subscripting an actual type, + # e.g. `list[int][0]`, is not a valid type and keeps the + # error: only a plain variable may root the chain. + root: Expression = expr.base + while isinstance(root, IndexExpr): + root = root.base + if not (isinstance(root, NameExpr) and isinstance(root.node, Var)): + raise TypeTranslationError() + if isinstance(expr.index, TupleExpr): + outer_args: list[Expression] = list(expr.index.items) + else: + outer_args = [expr.index] + return UnboundType( + base.name, + [ + *base.args, + *( + expr_to_unanalyzed_type( + arg, + options, + allow_new_syntax, + expr, + allow_unpack=True, + lookup_qualified=lookup_qualified, + ) + for arg in outer_args + ), + ], + line=base.line, + column=base.column, + ) if isinstance(expr.index, TupleExpr): args = expr.index.items else: diff --git a/mypy/nodes.py b/mypy/nodes.py index 20f7a0df804c..2d28e797ecdf 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -7,6 +7,7 @@ from collections import defaultdict from collections.abc import Callable, Iterator, Sequence from contextlib import contextmanager +from dataclasses import dataclass from enum import Enum, unique from typing import ( TYPE_CHECKING, @@ -3631,6 +3632,18 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: EXCLUDED_ENUM_ATTRIBUTES: Final = frozenset({"_ignore_", "_order_", "__order__"}) +@dataclass +class DeferredBaseClass: + """A base class that is a variable whose type was not yet inferred. + + The type checker validates these once the variable's type is known. + """ + + var: Var + expr: Expression + defining_literal: bool + + class TypeInfo(SymbolNode): """The type structure of a single class. @@ -3690,6 +3703,7 @@ class is generic then it will be a type constructor of higher kind. "type_object_type", "default_depends", "typeddict_data", + "deferred_base_classes", ) _fullname: str # Fully qualified name @@ -3924,6 +3938,11 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None self.dataclass_transform_spec = None self.is_type_check_only = False self.deprecated = None + # Base classes that are variables whose types had not been inferred yet + # during semantic analysis. The type checker validates them once the + # types are known (see TypeChecker.check_deferred_base_classes). + # This is not serialized; it is only meaningful within a single build. + self.deferred_base_classes: list[DeferredBaseClass] = [] self.type_object_type = None self.default_depends = {} self.typeddict_data = None diff --git a/mypy/semanal.py b/mypy/semanal.py index c079a865af1d..777718eb76ec 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -110,6 +110,7 @@ ContinueStmt, DataclassTransformSpec, Decorator, + DeferredBaseClass, DelStmt, DictExpr, DictionaryComprehension, @@ -243,6 +244,7 @@ from mypy.tvar_scope import TypeVarLikeScope from mypy.typeanal import ( SELF_TYPE_NAMES, + DeferredBaseClassVar, FindTypeVarVisitor, TypeAnalyser, TypeVarDefaultTranslator, @@ -2032,7 +2034,7 @@ def analyze_class(self, defn: ClassDef) -> None: self.mark_incomplete(defn.name, defn) return - base_types, base_error = bases_result + base_types, base_error, deferred_bases = bases_result if any(isinstance(base, PlaceholderType) for base, _ in base_types): # We need to know the TypeInfo of each base to construct the MRO. Placeholder types # are okay in nested positions, since they can't affect the MRO. @@ -2069,6 +2071,7 @@ def analyze_class(self, defn: ClassDef) -> None: defn.info.default_depends = default_depends if base_error: defn.info.fallback_to_any = True + defn.info.deferred_base_classes = deferred_bases if any_meta: defn.info.meta_fallback_to_any = True @@ -2486,8 +2489,11 @@ def analyze_unbound_tvar_impl( assert isinstance(sym.node, TypeVarExpr) return t.name, sym.node - def find_type_var_likes(self, t: Type) -> TypeVarLikeList: + def find_type_var_likes( + self, t: Type, *, suppress_name_errors: bool = False + ) -> TypeVarLikeList: visitor = FindTypeVarVisitor(self, self.tvar_scope) + visitor.suppress_name_errors = suppress_name_errors t.accept(visitor) return visitor.type_var_likes @@ -2503,7 +2509,7 @@ def get_all_bases_tvars( except TypeTranslationError: # This error will be caught later. continue - base_tvars = self.find_type_var_likes(base) + base_tvars = self.find_type_var_likes(base, suppress_name_errors=True) tvars.extend(base_tvars) return remove_dups(tvars) @@ -2610,7 +2616,7 @@ def get_name_repr_of_expr(self, expr: Expression) -> str | None: def analyze_base_classes( self, cls_name: str, base_type_exprs: list[Expression] - ) -> tuple[list[tuple[ProperType, Expression]], bool] | None: + ) -> tuple[list[tuple[ProperType, Expression]], bool, list[DeferredBaseClass]] | None: """Analyze base class types. Return None if some definition was incomplete. Otherwise, return a tuple @@ -2618,9 +2624,15 @@ def analyze_base_classes( * List of (analyzed type, original expression) tuples * Boolean indicating whether one of the bases had a semantic analysis error + * List of DeferredBaseClass for bases whose validity could not be + checked yet, because the variable's type has not been inferred + (inference happens in the type checker). These are provisionally + treated as having an Any base; the type checker validates them once + the type is known. """ is_error = False bases = [] + deferred: list[DeferredBaseClass] = [] for i, base_expr in enumerate(base_type_exprs): if ( isinstance(base_expr, RefExpr) @@ -2644,6 +2656,13 @@ def analyze_base_classes( allow_type_any=True, unique_name=inline_base(cls_name, i), ) + except DeferredBaseClassVar as e: + # The base is a variable whose type will only be known after + # type checking (e.g. `x = args[1]` where `args[1]` is `Any`). + # Provisionally treat it as Any; the type checker verifies the + # inferred type (see TypeChecker.check_deferred_base_classes). + deferred.append(DeferredBaseClass(e.var, base_expr, e.defining_literal)) + base = AnyType(TypeOfAny.special_form) except TypeTranslationError: name = self.get_name_repr_of_expr(base_expr) if isinstance(base_expr, CallExpr): @@ -2659,7 +2678,7 @@ def analyze_base_classes( return None base = get_proper_type(base) bases.append((base, base_expr)) - return bases, is_error + return bases, is_error, deferred def configure_base_classes( self, defn: ClassDef, bases: list[tuple[ProperType, Expression]] @@ -2683,11 +2702,16 @@ def configure_base_classes( base_types.append(base) elif isinstance(base, AnyType): if self.options.disallow_subclassing_any: - if isinstance(base_expr, (NameExpr, MemberExpr)): - msg = f'Class cannot subclass "{base_expr.name}" (has type "Any")' - else: - msg = 'Class cannot subclass value of type "Any"' - self.fail(msg, base_expr) + # A deferred base class is provisionally treated as Any; + # the type checker reports this error once the actual + # inferred type is known, if it is Any. + is_deferred = any(d.expr is base_expr for d in defn.info.deferred_base_classes) + if not is_deferred: + if isinstance(base_expr, (NameExpr, MemberExpr)): + msg = f'Class cannot subclass "{base_expr.name}" (has type "Any")' + else: + msg = 'Class cannot subclass value of type "Any"' + self.fail(msg, base_expr) info.fallback_to_any = True elif isinstance(base, TypedDictType): base_types.append(base.fallback) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index bd6f97cc94ce..d15207da7d7e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -186,6 +186,30 @@ def analyze_type_alias( return res, analyzer.aliases_used +class DeferredBaseClassVar(Exception): + """A base class is a variable whose type hasn't been inferred yet. + + Semantic analysis runs before type checking, so when a class uses a variable + with an inferred (not declared) type as a base class -- e.g. ``x = args[1]`` + where ``args[1]`` is ``Any`` -- the variable's type is not yet known. The + class is provisionally treated as having an ``Any`` base; the type checker + validates the inferred type once it is known (see + ``TypeChecker.check_deferred_base_classes``). + + The variable is carried on the exception so the checker can inspect its + inferred type without re-resolving the base class expression. + """ + + def __init__(self, var: nodes.Var, defining_literal: bool) -> None: + super().__init__() + self.var = var + # Whether the base class expression was being analyzed as part of a + # Literal[...] type. Semantic analysis does not report "not valid as a + # type" for variables in that position, so the type checker must not + # either. + self.defining_literal = defining_literal + + class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface): """Semantic analyzer for types. @@ -1018,6 +1042,23 @@ def analyze_unbound_type_without_type_info( return AnyType(TypeOfAny.special_form) if isinstance(typ, TypeType) and isinstance(typ.item, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=typ.item) + if typ is None and self.nesting_level == 0: + # The variable's type has not been inferred yet (inference + # happens in the type checker, after semantic analysis). + # Defer the validity check; the type checker will verify + # the inferred type. Provisionally treat the base as Any. + # Only the base class itself may be deferred: a variable + # nested inside another base type (e.g. a type argument, + # as in `tuple[T]`) is not a base class, so it keeps the + # normal "not valid as a type" error below. + raise DeferredBaseClassVar(sym.node, defining_literal) + if t.args and self.nesting_level == 0: + # The base class subscripts the variable, e.g. `args[1]` + # where `args` is a *args tuple. Whether the base is valid + # depends on the type of the subscripted item, which the + # type checker computes (the variable's type may itself be + # inferred). Defer the validity check, as above. + raise DeferredBaseClassVar(sym.node, defining_literal) # Option 2: # Unbound type variable. Currently these may be still valid, # for example when defining a generic type alias. @@ -2765,6 +2806,13 @@ def __init__(self, api: SemanticAnalyzerCoreInterface, scope: TypeVarLikeScope) self.type_var_likes: list[tuple[str, TypeVarLikeExpr]] = [] self.has_self_type = False self.include_callables = True + # Only set when scanning base classes for type variables: an argument + # of a subscripted base may be a value, not a type name (e.g. the + # string key in `kwargs['some_any_field']`, converted to an + # UnboundType by parse_type_string), so an unknown name there is not + # an error. Undefined type names in bases are reported when the + # bases are analyzed. + self.suppress_name_errors = False def _seems_like_callable(self, type: UnboundType) -> bool: if not type.args: @@ -2773,7 +2821,7 @@ def _seems_like_callable(self, type: UnboundType) -> bool: def visit_unbound_type(self, t: UnboundType) -> None: name = t.name - node = self.api.lookup_qualified(name, t) + node = self.api.lookup_qualified(name, t, suppress_errors=self.suppress_name_errors) if node and node.fullname in SELF_TYPE_NAMES: self.has_self_type = True if ( diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b6eee21c50af..81ca3e5f02f6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -9794,6 +9794,369 @@ class C: reveal_type(C.x) # N: Revealed type is "builtins.int | None" [builtins fixtures/classmethod.pyi] +[case testSubclassInferredAnyVariable] +# flags: --python-version=3.11 +from typing import Any +def f(*args: *tuple[int, Any]) -> Any: + x = args[1] + class Sub(x): pass + return Sub() +[builtins fixtures/tuple.pyi] + +[case testSubclassInferredNonAnyVariable] +from typing import Any +def f(x: Any) -> None: + y = x + class Sub1(y): pass + z = 1 + class Sub2(z): pass # E: Variable "z" 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 "z" + +[case testSubclassInferredLeakedAnyStillInvalid] +from typing import Any +def g(a) -> None: + # Any leaking in from unannotated code is still rejected. + x = a + 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" + +[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_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 testSubclassAnyFromStarAny] +# https://github.com/python/mypy/issues/21998 +from typing import Any + +def from_star_any(*args: Any) -> None: + class Sub(args[0]): + 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: + # An unannotated local initialized from an Any value is inferred as + # explicit Any, so it is accepted as a base class. + x = base + class Sub(x): pass +[builtins fixtures/tuple.pyi] + +[case testSubclassAnyFromNestedUnpackedUnboundedStarArgs] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def from_unpacked(*args: *tuple[int, *tuple[int, ...], Any]) -> None: + reveal_type(args[-1]) # N: Revealed type is "Any" + class Sub(args[-1]): + pass + +def from_unpacked_type_annotation(*args: *tuple[int, *tuple[int, ...], Any]) -> None: + reveal_type(args[-1]) # N: Revealed type is "Any" + x : type = args[-1] + class Sub(x): + pass +[builtins fixtures/tuple.pyi] + +[case testSubclassAnyFromUnpackedStarArgsAmbiguousIndex] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def inside_the_unpack(*args: *tuple[int, *tuple[Any, ...], int]) -> None: + reveal_type(args[1]) # N: Revealed type is "Any | builtins.int" + class Sub(args[1]): # 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[1] instead? + pass + +def across_the_unpack(*args: *tuple[int, *tuple[Any, ...], int]) -> None: + reveal_type(args[-2]) # N: Revealed type is "builtins.int | Any" + class Sub(args[-2]): # 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[-2] instead? + pass +[builtins fixtures/tuple.pyi] + +[case testSubclassAnyFromUnpackedStarArgsUnambiguousIndex] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def before_the_unpack(*args: *tuple[Any, *tuple[int, ...], int]) -> None: + reveal_type(args[0]) # N: Revealed type is "Any" + class Sub(args[0]): + pass + +def after_the_unpack(*args: *tuple[int, *tuple[int, ...], Any]) -> None: + reveal_type(args[-1]) # N: Revealed type is "Any" + class Sub(args[-1]): + pass + +def across_or_inside_the_unpack_one_or_more_any(*args: *tuple[int, *tuple[Any, ...], Any, int]) -> None: + reveal_type(args[1]) # N: Revealed type is "Any" + class Sub(args[1]): + pass + +def across_or_inside_the_unpack_one_or_more_any2(*args: *tuple[int, Any, *tuple[Any, ...], int]) -> None: + reveal_type(args[-2]) # N: Revealed type is "Any" + class Sub(args[-2]): + pass +[builtins fixtures/tuple.pyi] + +[case testSubclassTypeOrAnyFromUnpackedStarArgsAmbiguousIndex] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def across_the_unpack_ambiguous_with_type_should_be_fine(*args: *tuple[type, *tuple[Any, ...], int]) -> None: + reveal_type(args[-2]) # N: Revealed type is "builtins.type | Any" + class Sub(args[-2]): + pass + +def across_the_unpack_ambiguous_with_type_should_be_fine2(*args: *tuple[int, *tuple[Any, ...], type]) -> None: + reveal_type(args[1]) # N: Revealed type is "Any | builtins.type" + class Sub(args[1]): + pass +[builtins fixtures/tuple.pyi] + +[case testSubclassAnyFromNestedUnpackedUnboundedStarArgsIndexInsideUnambiguous] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def from_unpacked(*args: *tuple[Any, ...]) -> None: + reveal_type(args[0]) # N: Revealed type is "Any" + reveal_type(args[1]) # N: Revealed type is "Any" + class Sub(args[0]): + pass + class Sub2(args[1]): + pass + +def from_unpacked2(*args: *tuple[*tuple[Any, ...]]) -> None: + reveal_type(args[0]) # N: Revealed type is "Any" + reveal_type(args[1]) # N: Revealed type is "Any" + class Sub(args[0]): + pass + class Sub2(args[1]): + pass + class Sub3(args[-(-1)]): + pass + +def from_unpacked_type_annotation(*args: *tuple[Any, ...]) -> None: + reveal_type(args[0]) # N: Revealed type is "Any" + reveal_type(args[1]) # N: Revealed type is "Any" + x: type = args[0] + class Sub(x): + pass + x1: type = args[1] + class Sub2(x1): + pass + +def from_unpacked2_type_annotation(*args: *tuple[*tuple[Any, ...]]) -> None: + reveal_type(args[0]) # N: Revealed type is "Any" + reveal_type(args[1]) # N: Revealed type is "Any" + x: type = args[0] + class Sub(x): + pass + x1: type = args[1] + class Sub2(x1): + pass +[builtins fixtures/tuple.pyi] + +[case testSubclassFromSequenceIndexed] +from typing import Any, Sequence + +def f(y: Sequence[Any]) -> None: + class A(y[0]): pass +[builtins fixtures/tuple.pyi] + +[case testSubclassFromUnpackedStarArgsAnyTypedList] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def fun_varargs_list_wrapped_element_to_type_used_for_inherit( + *args: *tuple[int, list[Any]], +) -> Any: + class Sub(args[1][0]): pass + +[builtins fixtures/tuple.pyi] + +[case testSubclassFromAnyTypedList] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def fun_list_wrapped_element_to_type_used_for_inherit( + a: list[Any], +) -> Any: + class Sub(a[0]): pass + +[builtins fixtures/tuple.pyi] + +[case testSubclassFromUnpackedStarArgsNonAnyTypedList] +# https://github.com/python/mypy/issues/21998 +# flags: --python-version 3.12 +from typing import Any + +def fun_varargs_int_list_element_used_for_inherit( + *args: *tuple[int, list[int]], +) -> Any: + class Sub(args[1][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? + +[builtins fixtures/tuple.pyi] + +[case testDeferredBaseClassUncheckedFunction] +# Dynamic base classes in unchecked functions were silently accepted before; +# the deferred checker must not report new errors there. +def f(base): + class A(base): + pass + return A +def g(x): + y = x + class B(y): + pass +[builtins fixtures/tuple.pyi] + +[case testDeferredBaseClassInLiteral] +# A variable inside Literal[...] in a base class: semanal does not report +# "not valid as a type" there, so the deferred checker must not either. +from typing import Literal, Generic, TypeVar +T = TypeVar("T") +class B(Generic[T]): pass +x = 1 +class C(B["Literal[x]"]): pass # E: Parameter 1 of Literal[...] is invalid +[builtins fixtures/tuple.pyi] + +[case testSubclassVariableNestedInBaseClassArgs] +# A variable nested inside a base class's type arguments is not itself a +# base class, so it must not be deferred: it gets the normal "not valid as +# a type" error. Regression test for the mypy_primer diff on prefect, +# where `class BaseAnnotation(tuple[T])` gained an "Invalid base class" +# error for the type argument. +from typing import Any +def f(v: Any) -> None: + x = v + class Sub(tuple[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 +[builtins fixtures/tuple.pyi] + +[case testSubclassAnyFromKwargsTypedDictEntry] +# https://github.com/python/mypy/issues/22309 +from typing import Any, TypedDict +from typing_extensions import Unpack + +class Person(TypedDict): + name: str + age: int + some_any_field: Any + +def foo(arg: bool, **kwargs: Unpack[Person]) -> None: + class Sub(kwargs["some_any_field"]): pass +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testSubclassAnyFromKwargsDict] +# https://github.com/python/mypy/issues/22309 +from typing import Any + +def foo(**kwargs: Any) -> None: + class Sub(kwargs["some_any_field"]): pass +[builtins fixtures/dict.pyi] + +[case testSubclassNonAnyFromKwargsTypedDictEntry] +# https://github.com/python/mypy/issues/22309 +from typing import Any, TypedDict +from typing_extensions import Unpack + +class Person(TypedDict): + name: str + age: int + some_any_field: Any + +def foo(arg: bool, **kwargs: Unpack[Person]) -> None: + class Sub(kwargs["name"]): pass # E: Variable "kwargs" 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 "kwargs" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypeVarDefaultErrorLocation] from typing import Generic