diff --git a/mypy/checker.py b/mypy/checker.py index 49f1bc15f583..27b71b957efc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -117,7 +117,6 @@ TypeAlias, TypeAliasStmt, TypeInfo, - TypeVarExpr, UnaryExpr, Var, WhileStmt, @@ -2858,29 +2857,6 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None: if name in base2.names and base2 not in base.mro: self.check_compatibility(name, base, base2, typ) - def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None: - # TODO: this duplicates both checkmember.py and analyze_ref_expr(), delete. - if sym.type is not None: - return sym.type - if isinstance(sym.node, SYMBOL_FUNCBASE_TYPES): - return self.function_type(sym.node) - if isinstance(sym.node, TypeInfo): - if sym.node.typeddict_type: - # We special-case TypedDict, because they don't define any constructor. - return self.expr_checker.typeddict_callable(sym.node) - else: - return type_object_type(sym.node, self.named_type) - if isinstance(sym.node, TypeVarExpr): - # Use of TypeVars is rejected in an expression/runtime context, so - # we don't need to check supertype compatibility for them. - return AnyType(TypeOfAny.special_form) - if isinstance(sym.node, TypeAlias): - with self.msg.filter_errors(): - # Suppress any errors, they will be given when analyzing the corresponding node. - # Here we may have incorrect options and location context. - return self.expr_checker.alias_type_in_runtime_context(sym.node, ctx=sym.node) - return None - def check_compatibility( self, name: str, base1: TypeInfo, base2: TypeInfo, ctx: TypeInfo ) -> None: diff --git a/mypy/checker_shared.py b/mypy/checker_shared.py index 6c62af50466c..2ab4548edfaf 100644 --- a/mypy/checker_shared.py +++ b/mypy/checker_shared.py @@ -21,7 +21,7 @@ MypyFile, Node, RefExpr, - TypeAlias, + SymbolNode, TypeInfo, Var, ) @@ -64,10 +64,6 @@ def accept( def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: raise NotImplementedError - @abstractmethod - def module_type(self, node: MypyFile) -> Instance: - raise NotImplementedError - @abstractmethod def check_call( self, @@ -112,12 +108,6 @@ def check_method_call_by_name( ) -> tuple[Type, Type]: raise NotImplementedError - @abstractmethod - def alias_type_in_runtime_context( - self, alias: TypeAlias, *, ctx: Context, alias_definition: bool = False - ) -> Type: - raise NotImplementedError - @abstractmethod def visit_typeddict_index_expr( self, td_type: TypedDictType, index: Expression, setitem: bool = False @@ -125,11 +115,19 @@ def visit_typeddict_index_expr( raise NotImplementedError @abstractmethod - def typeddict_callable(self, info: TypeInfo) -> CallableType: + def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Type: raise NotImplementedError @abstractmethod - def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Type: + def analyze_static_reference( + self, + node: SymbolNode, + ctx: Context, + is_lvalue: bool, + *, + include_modules: bool = True, + suppress_errors: bool = False, + ) -> Type: raise NotImplementedError diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e0c7e829309c..0b454a23a785 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -7,7 +7,7 @@ import time from collections import defaultdict from collections.abc import Iterable, Iterator, Sequence -from contextlib import contextmanager +from contextlib import contextmanager, nullcontext from typing import Callable, ClassVar, Final, Optional, cast, overload from typing_extensions import TypeAlias as _TypeAlias, assert_never @@ -94,6 +94,7 @@ TypedDictExpr, TypeInfo, TypeVarExpr, + TypeVarLikeExpr, TypeVarTupleExpr, UnaryExpr, Var, @@ -173,6 +174,7 @@ TypeOfAny, TypeType, TypeVarId, + TypeVarLikeType, TypeVarTupleType, TypeVarType, UnboundType, @@ -377,9 +379,8 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = self.analyze_var_ref(node, e) if isinstance(result, PartialType): result = self.chk.handle_partial_var_type(result, lvalue, node, e) - elif isinstance(node, FuncDef): - # Reference to a global function. - result = function_type(node, self.named_type("builtins.function")) + elif isinstance(node, Decorator): + result = self.analyze_var_ref(node.var, e) elif isinstance(node, OverloadedFuncDef): if node.type is None: if self.chk.in_checked_function() and node.items: @@ -387,16 +388,15 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = AnyType(TypeOfAny.from_error) else: result = node.type - elif isinstance(node, TypeInfo): - # Reference to a type object. - if node.typeddict_type: - # We special-case TypedDict, because they don't define any constructor. - result = self.typeddict_callable(node) - elif node.fullname == "types.NoneType": - # We special case NoneType, because its stub definition is not related to None. - result = TypeType(NoneType()) - else: - result = type_object_type(node, self.named_type) + elif isinstance(node, (FuncDef, TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)): + result = self.analyze_static_reference(node, e, e.is_alias_rvalue or lvalue) + else: + if isinstance(node, PlaceholderNode): + assert False, f"PlaceholderNode {node.fullname!r} leaked to checker" + # Unknown reference; use any type implicitly to avoid + # generating extra type errors. + result = AnyType(TypeOfAny.from_error) + if isinstance(node, TypeInfo): if isinstance(result, CallableType) and isinstance( # type: ignore[misc] result.ret_type, Instance ): @@ -408,30 +408,56 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: # This is the type in a type[] expression, so substitute type # variables with Any. result = erasetype.erase_typevars(result) - elif isinstance(node, MypyFile): - # Reference to a module object. - result = self.module_type(node) - elif isinstance(node, Decorator): - result = self.analyze_var_ref(node.var, e) + assert result is not None + return result + + def analyze_static_reference( + self, + node: SymbolNode, + ctx: Context, + is_lvalue: bool, + *, + include_modules: bool = True, + suppress_errors: bool = False, + ) -> Type: + """ + This is the version of analyze_ref_expr() that doesn't do any deferrals. + + This function can be used by member access to "static" attributes. For example, + when accessing module attributes in protocol checks, or accessing attributes of + special kinds (like TypeAlias, TypeInfo, etc.) on an instance or class object. + # TODO: merge with analyze_ref_expr() when we are confident about performance. + """ + if isinstance(node, (Var, Decorator, OverloadedFuncDef)): + return node.type or AnyType(TypeOfAny.special_form) + elif isinstance(node, FuncDef): + return function_type(node, self.named_type("builtins.function")) + elif isinstance(node, TypeInfo): + # Reference to a type object. + if node.typeddict_type: + # We special-case TypedDict, because they don't define any constructor. + return self.typeddict_callable(node) + elif node.fullname == "types.NoneType": + # We special case NoneType, because its stub definition is not related to None. + return TypeType(NoneType()) + else: + return type_object_type(node, self.named_type) elif isinstance(node, TypeAlias): # Something that refers to a type alias appears in runtime context. # Note that we suppress bogus errors for alias redefinitions, # they are already reported in semanal.py. - result = self.alias_type_in_runtime_context( - node, ctx=e, alias_definition=e.is_alias_rvalue or lvalue - ) + with self.msg.filter_errors() if suppress_errors else nullcontext(): + return self.alias_type_in_runtime_context( + node, ctx=ctx, alias_definition=is_lvalue + ) elif isinstance(node, TypeVarExpr): return self.named_type("typing.TypeVar") elif isinstance(node, (ParamSpecExpr, TypeVarTupleExpr)): - result = self.object_type() - else: - if isinstance(node, PlaceholderNode): - assert False, f"PlaceholderNode {node.fullname!r} leaked to checker" - # Unknown reference; use any type implicitly to avoid - # generating extra type errors. - result = AnyType(TypeOfAny.from_error) - assert result is not None - return result + return self.object_type() + elif isinstance(node, MypyFile): + # Reference to a module object. + return self.module_type(node) if include_modules else AnyType(TypeOfAny.special_form) + return AnyType(TypeOfAny.from_error) def analyze_var_ref(self, var: Var, context: Context) -> Type: if var.type: @@ -459,20 +485,21 @@ def module_type(self, node: MypyFile) -> Instance: # Fall back to a dummy 'object' type instead to # avoid a crash. result = self.named_type("builtins.object") - module_attrs = {} + module_attrs: dict[str, Type] = {} immutable = set() for name, n in node.names.items(): if not n.module_public: continue if isinstance(n.node, Var) and n.node.is_final: immutable.add(name) - typ = self.chk.determine_type_of_member(n) - if typ: - module_attrs[name] = typ + if n.node is None: + module_attrs[name] = AnyType(TypeOfAny.from_error) else: # TODO: what to do about nested module references? # They are non-trivial because there may be import cycles. - module_attrs[name] = AnyType(TypeOfAny.special_form) + module_attrs[name] = self.analyze_static_reference( + n.node, n.node, False, include_modules=False, suppress_errors=True + ) result.extra_attrs = ExtraAttrs(module_attrs, immutable, node.fullname) return result @@ -961,19 +988,11 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType: assert info.special_alias is not None target = info.special_alias.target assert isinstance(target, ProperType) and isinstance(target, TypedDictType) - expected_types = list(target.items.values()) - kinds = [ArgKind.ARG_NAMED] * len(expected_types) - names = list(target.items.keys()) - return CallableType( - expected_types, - kinds, - names, - target, - self.named_type("builtins.type"), - variables=info.defn.type_vars, - ) + return self.typeddict_callable_from_context(target, info.defn.type_vars) - def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType: + def typeddict_callable_from_context( + self, callee: TypedDictType, variables: Sequence[TypeVarLikeType] | None = None + ) -> CallableType: return CallableType( list(callee.items.values()), [ @@ -983,6 +1002,8 @@ def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType list(callee.items.keys()), callee, self.named_type("builtins.type"), + variables=variables, + is_bound=True, ) def check_typeddict_call_with_kwargs( diff --git a/mypy/checkmember.py b/mypy/checkmember.py index beb3c1397c11..ec8d90b9e4bd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -34,7 +34,7 @@ TempNode, TypeAlias, TypeInfo, - TypeVarExpr, + TypeVarLikeExpr, Var, is_final_node, ) @@ -49,7 +49,6 @@ make_simplified_union, supported_self_type, tuple_fallback, - type_object_type, ) from mypy.types import ( AnyType, @@ -537,24 +536,20 @@ def analyze_member_var_access( is_trivial_self = vv.func.is_trivial_self and not vv.decorators if mx.is_super and not mx.suppress_errors: validate_super_call(vv.func, mx) + if isinstance(v, FuncDef): + assert False, "Did not expect a function" + if isinstance(v, MypyFile): + mx.chk.module_refs.add(v.fullname) - if isinstance(vv, TypeInfo): + if isinstance(vv, (TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)): # If the associated variable is a TypeInfo synthesize a Var node for # the purposes of type checking. This enables us to type check things - # like accessing class attributes on an inner class. - v = Var(name, type=type_object_type(vv, mx.named_type)) - v.info = info - - if isinstance(vv, TypeAlias): - # Similar to the above TypeInfo case, we allow using - # qualified type aliases in runtime context if it refers to an - # instance type. For example: + # like accessing class attributes on an inner class. Similar we allow + # using qualified type aliases in runtime context. For example: # class C: # A = List[int] # x = C.A() <- this is OK - typ = mx.chk.expr_checker.alias_type_in_runtime_context( - vv, ctx=mx.context, alias_definition=mx.is_lvalue - ) + typ = mx.chk.expr_checker.analyze_static_reference(vv, mx.context, mx.is_lvalue) v = Var(name, type=typ) v.info = info @@ -567,13 +562,6 @@ def analyze_member_var_access( check_final_member(name, info, mx.msg, mx.context) return analyze_var(name, v, itype, mx, implicit=implicit, is_trivial_self=is_trivial_self) - elif isinstance(v, FuncDef): - assert False, "Did not expect a function" - elif isinstance(v, MypyFile): - mx.chk.module_refs.add(v.fullname) - return mx.chk.expr_checker.module_type(v) - elif isinstance(v, TypeVarExpr): - return mx.chk.named_type("typing.TypeVar") elif ( not v and name not in ["__getattr__", "__setattr__", "__getattribute__"] @@ -1259,29 +1247,9 @@ def analyze_class_attribute_access( mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.special_form) - if isinstance(node.node, TypeVarExpr): - mx.fail(message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format(info.name, name)) - return AnyType(TypeOfAny.from_error) - - # TODO: some logic below duplicates analyze_ref_expr in checkexpr.py - if isinstance(node.node, TypeInfo): - if node.node.typeddict_type: - # We special-case TypedDict, because they don't define any constructor. - return mx.chk.expr_checker.typeddict_callable(node.node) - elif node.node.fullname == "types.NoneType": - # We special case NoneType, because its stub definition is not related to None. - return TypeType(NoneType()) - else: - return type_object_type(node.node, mx.named_type) - - if isinstance(node.node, MypyFile): - # Reference to a module object. - return mx.named_type("types.ModuleType") - - if isinstance(node.node, TypeAlias): - return mx.chk.expr_checker.alias_type_in_runtime_context( - node.node, ctx=mx.context, alias_definition=mx.is_lvalue - ) + if isinstance(node.node, (TypeInfo, TypeAlias, MypyFile, TypeVarLikeExpr)): + # TODO: should we apply class plugin here (similar to instance access)? + return mx.chk.expr_checker.analyze_static_reference(node.node, mx.context, mx.is_lvalue) if is_decorated: assert isinstance(node.node, Decorator) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 0c7464246990..609f968a8c65 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -188,7 +188,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage: # TypeVar INCOMPATIBLE_TYPEVAR_VALUE: Final = 'Value of type variable "{}" of {} cannot be {}' -CANNOT_USE_TYPEVAR_AS_EXPRESSION: Final = 'Type variable "{}.{}" cannot be used as an expression' INVALID_TYPEVAR_AS_TYPEARG: Final = 'Type variable "{}" not valid as type argument value for "{}"' INVALID_TYPEVAR_ARG_BOUND: Final = 'Type argument {} of "{}" must be a subtype of {}' INVALID_TYPEVAR_ARG_VALUE: Final = 'Invalid type argument value for "{}"' diff --git a/mypyc/test-data/fixtures/typing-full.pyi b/mypyc/test-data/fixtures/typing-full.pyi index 6b6aba6802b1..d37129bc2e0b 100644 --- a/mypyc/test-data/fixtures/typing-full.pyi +++ b/mypyc/test-data/fixtures/typing-full.pyi @@ -12,12 +12,14 @@ class GenericMeta(type): pass class _SpecialForm: def __getitem__(self, index): ... +class TypeVar: + def __init__(self, name, *args, bound=None): ... + def __or__(self, other): ... cast = 0 overload = 0 Any = object() Optional = 0 -TypeVar = 0 Generic = 0 Protocol = 0 Tuple = 0 diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index 91a6103e31ae..46f343fa3798 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1286,6 +1286,7 @@ def bar() -> None: print(inner.__dict__) # type: ignore bar() +[typing fixtures/typing-full.pyi] [out] {'__module__': 'native', '__name__': 'bar', '__qualname__': 'bar', '__doc__': None, '__wrapped__': } diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c7136509729e..23c0d4ccf316 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7902,8 +7902,7 @@ class Foo: from mod import meth2 # E: Unsupported class scoped import from mod import T -reveal_type(Foo.T) # E: Type variable "Foo.T" cannot be used as an expression \ - # N: Revealed type is "Any" +reveal_type(Foo.T) # N: Revealed type is "typing.TypeVar" [file mod.pyi] from typing import Any, TypeVar, overload @@ -7915,6 +7914,8 @@ def meth1(self: Any, y: str) -> str: ... T = TypeVar("T") def meth2(self: Any, y: T) -> T: ... +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] [case testNewAndInitNoReturn] from typing import NoReturn diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index ded390067de0..30d8497c9cd2 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2647,3 +2647,22 @@ User("", 0) # E: Too many positional arguments for "User" User("", id=0) User("", name="") # E: "User" gets multiple values for keyword argument "name" [builtins fixtures/tuple.pyi] + +[case testDataclassDefaultFactoryTypedDict] +from dataclasses import dataclass, field +from mypy_extensions import TypedDict + +class Person(TypedDict, total=False): + name: str + +@dataclass +class Job: + person: Person = field(default_factory=Person) + +class PersonBad(TypedDict): + name: str + +@dataclass +class JobBad: + person: PersonBad = field(default_factory=PersonBad) # E: Argument "default_factory" to "field" has incompatible type "type[PersonBad]"; expected "Callable[[], PersonBad]" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 5ae4b4e57176..858024e7daf2 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1582,8 +1582,8 @@ def f() -> types.ModuleType: return types reveal_type(f()) # N: Revealed type is "types.ModuleType" reveal_type(types) # N: Revealed type is "types.ModuleType" - [builtins fixtures/module.pyi] +[typing fixtures/typing-full.pyi] [case testClassImportAccessedInMethod] class C: @@ -1997,6 +1997,7 @@ from typing import TypeVar T = TypeVar('T') def whatever(x: T) -> T: pass [builtins fixtures/module.pyi] +[typing fixtures/typing-full.pyi] [case testModuleAliasToQualifiedImport2] import mod @@ -2012,8 +2013,8 @@ from typing import TypeVar T = TypeVar('T') def whatever(x: T) -> T: pass [file othermod.py] - [builtins fixtures/module.pyi] +[typing fixtures/typing-full.pyi] [case testModuleLevelGetattr] import has_getattr diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 1d489d54409f..61bf08018722 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2805,6 +2805,7 @@ def get() -> int: ... import typing t = typing.typevar('t') # E: Module has no attribute "typevar" [builtins fixtures/module.pyi] +[typing fixtures/typing-full.pyi] [case testNewAnalyzerImportFromTopLevelFunction] import a.b # This works at runtime diff --git a/test-data/unit/check-redefine.test b/test-data/unit/check-redefine.test index 7ddfdd0f8a4f..4bcbaf50298d 100644 --- a/test-data/unit/check-redefine.test +++ b/test-data/unit/check-redefine.test @@ -351,6 +351,7 @@ def f() -> None: n = 1 import typing as n # E: Incompatible import of "n" (imported name has type Module, local name has type "int") [builtins fixtures/module.pyi] +[typing fixtures/typing-full.pyi] [case testRedefineLocalWithTypeAnnotation] # flags: --allow-redefinition @@ -547,6 +548,7 @@ try: except Exception as typing: pass [builtins fixtures/exception.pyi] +[typing fixtures/typing-full.pyi] [case testRedefiningUnderscoreFunctionIsntAnError] def _(arg): diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 6bcc6e20328b..42a71373ac52 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -4258,3 +4258,17 @@ e1: E = {"x": 0, "y": "a"} e2: E = {"x": "no", "y": "a"} [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testTypedDictAliasAsInstanceAttribute] +from typing import TypedDict + +class Dicts: + class TF(TypedDict, total=False): + user_id: int + TotalFalse = TF + +dicts = Dicts() +reveal_type(dicts.TF) # N: Revealed type is "def (*, user_id: builtins.int =) -> TypedDict('__main__.Dicts.TF', {'user_id'?: builtins.int})" +reveal_type(dicts.TotalFalse) # N: Revealed type is "def (*, user_id: builtins.int =) -> TypedDict('__main__.Dicts.TF', {'user_id'?: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index ab2956374c12..1be75c0f4706 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -592,11 +592,10 @@ class C: def f(self, x: T) -> T: L = List[S] y: L[C.T] = [x] - C.T # E: Type variable "C.T" cannot be used as an expression - A = C.T # E: Type variable "C.T" cannot be used as an expression + reveal_type(C.T) # N: Revealed type is "typing.TypeVar" return y[0] - [builtins fixtures/list.pyi] +[typing fixtures/typing-full.pyi] [case testTypeVarWithAnyTypeBound] # flags: --follow-imports=skip diff --git a/test-data/unit/fixtures/exception.pyi b/test-data/unit/fixtures/exception.pyi index 08496e4e5934..963192cc86ab 100644 --- a/test-data/unit/fixtures/exception.pyi +++ b/test-data/unit/fixtures/exception.pyi @@ -12,6 +12,7 @@ class list: pass class dict: pass class function: pass class int: pass +class float: pass class str: pass class bool: pass class ellipsis: pass