From a430c123b8faf300af188daebf043eabaa096b80 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 29 Oct 2023 21:32:21 +0100 Subject: [PATCH 01/21] Interpret `type` annotations as `type[Any]` annotations. --- mypy/messages.py | 2 +- mypy/semanal.py | 8 +++++- mypy/stubgenc.py | 2 +- mypy/typeanal.py | 6 ++++- test-data/unit/check-generic-alias.test | 2 +- test-data/unit/check-lowercase.test | 4 +-- test-data/unit/check-narrowing.test | 33 ++++++++++++++++++++++++- 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 19aafedd5586..adf4ba7dca78 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2068,7 +2068,7 @@ def report_protocol_problems( # note: method, attr MAX_ITEMS = 2 # Maximum number of conflicts, missing members, and overloads shown # List of special situations where we don't want to report additional problems - exclusions: dict[type, list[str]] = { + exclusions: dict[type[Any], list[str]] = { TypedDictType: ["typing.Mapping"], TupleType: ["typing.Iterable", "typing.Sequence"], } diff --git a/mypy/semanal.py b/mypy/semanal.py index 179ee7c70bfb..1e19037941bb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3384,7 +3384,9 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: if s.type: lvalue = s.lvalues[-1] allow_tuple_literal = isinstance(lvalue, TupleExpr) - analyzed = self.anal_type(s.type, allow_tuple_literal=allow_tuple_literal) + analyzed = self.anal_type( + s.type, allow_tuple_literal=allow_tuple_literal, analyze_annotation=True + ) # Don't store not ready types (including placeholders). if analyzed is None or has_placeholder(analyzed): self.defer(s) @@ -6602,6 +6604,7 @@ def type_analyzer( report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, + analyze_annotation: bool = False, ) -> TypeAnalyser: if tvar_scope is None: tvar_scope = self.tvar_scope @@ -6620,6 +6623,7 @@ def type_analyzer( allow_unpack=allow_unpack, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, + analyze_annotation=analyze_annotation, ) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack @@ -6644,6 +6648,7 @@ def anal_type( report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, + analyze_annotation: bool = False, third_pass: bool = False, ) -> Type | None: """Semantically analyze a type. @@ -6682,6 +6687,7 @@ def anal_type( report_invalid_types=report_invalid_types, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, + analyze_annotation=analyze_annotation, ) tag = self.track_incomplete_refs() typ = typ.accept(a) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 0ad79a4265b3..fa795d1ffebd 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -707,7 +707,7 @@ def get_base_types(self, obj: type) -> list[str]: # remove the class itself all_bases = all_bases[1:] # Remove base classes of other bases as redundant. - bases: list[type] = [] + bases: list[type[Any]] = [] for base in all_bases: if not any(issubclass(b, base) for b in bases): bases.append(base) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index b16d0ac066b4..4ea315ea4ba2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -202,6 +202,7 @@ def __init__( prohibit_self_type: str | None = None, allowed_alias_tvars: list[TypeVarLikeType] | None = None, allow_type_any: bool = False, + analyze_annotation: bool = False, ) -> None: self.api = api self.fail_func = api.fail @@ -246,6 +247,7 @@ def __init__( self.allow_type_any = allow_type_any self.allow_type_var_tuple = False self.allow_unpack = allow_unpack + self.analyze_annotation = analyze_annotation def lookup_qualified( self, name: str, ctx: Context, suppress_errors: bool = False @@ -588,7 +590,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ and (self.always_allow_new_syntax or self.options.python_version >= (3, 9)) ): if len(t.args) == 0: - if fullname == "typing.Type": + if fullname == "typing.Type" or ( + self.analyze_annotation and (fullname == "builtins.type") + ): any_type = self.get_omitted_any(t) return TypeType(any_type, line=t.line, column=t.column) else: diff --git a/test-data/unit/check-generic-alias.test b/test-data/unit/check-generic-alias.test index 8c90b5adba34..1ef177a7a7e8 100644 --- a/test-data/unit/check-generic-alias.test +++ b/test-data/unit/check-generic-alias.test @@ -109,7 +109,7 @@ reveal_type(t6) # N: Revealed type is "Tuple[builtins.int, builtins.str]" reveal_type(t7) # N: Revealed type is "builtins.tuple[builtins.int, ...]" reveal_type(t8) # N: Revealed type is "builtins.dict[Any, Any]" reveal_type(t9) # N: Revealed type is "builtins.dict[builtins.int, builtins.str]" -reveal_type(t10) # N: Revealed type is "builtins.type" +reveal_type(t10) # N: Revealed type is "Type[Any]" reveal_type(t11) # N: Revealed type is "Type[builtins.int]" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-lowercase.test b/test-data/unit/check-lowercase.test index d1ebbdd282fa..28312d5a5802 100644 --- a/test-data/unit/check-lowercase.test +++ b/test-data/unit/check-lowercase.test @@ -45,7 +45,7 @@ x = 3 # E: Incompatible types in assignment (expression has type "int", variabl [case testTypeLowercaseSettingOff] # flags: --python-version 3.9 --no-force-uppercase-builtins -x: type[type] +x: type[type] # E: Type[...] can't contain another Type[...] y: int -y = x # E: Incompatible types in assignment (expression has type "type[type]", variable has type "int") +y = x # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "int") diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 5b7fadf41c79..4614b4c2b7eb 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -659,6 +659,37 @@ else: reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" [builtins fixtures/primitives.pyi] +[case testNarrowingTypingTypeTypeAndBuiltinTypeTypeExplicitAny] +# flags: --python-version 3.9 --warn-unreachable --disallow-any-generics + +from typing import Any, Type + +t1: Type[Any] +t2: type[Any] + +class C: ... + +if isinstance(t1, C): + reveal_type(t1) # E: Statement is unreachable +if isinstance(t2, C): + reveal_type(t2) # E: Statement is unreachable +[builtins fixtures/isinstance.pyi] + +[case testNarrowingTypingTypeTypeAndBuiltinTypeTypeImplicitAny] +# flags: --python-version 3.9 --warn-unreachable --disallow-any-generics + +from typing import Type + +t1: Type # E: Missing type parameters for generic type "Type" +t2: type # E: Missing type parameters for generic type "type" +class C: ... + +if isinstance(t1, C): + reveal_type(t1) # E: Statement is unreachable +if isinstance(t2, C): + reveal_type(t2) # E: Statement is unreachable +[builtins fixtures/isinstance.pyi] + [case testNarrowingExprPropagation] from typing import Union from typing_extensions import Literal @@ -1330,7 +1361,7 @@ else: raw: tuple[type, ...] if isinstance(some, raw): - reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" + reveal_type(some) # N: Revealed type is "Any" else: reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" [builtins fixtures/dict.pyi] From 56fb2bdcc1f4fb7eb5b51e964e9afb949349c85f Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Mon, 13 Nov 2023 16:10:56 +0100 Subject: [PATCH 02/21] Replace `type` with `type[object]` in Mypy's code. --- mypy/fastparse.py | 2 +- mypy/messages.py | 2 +- mypy/stubgenc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 95d99db84a15..171379217621 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -373,7 +373,7 @@ def __init__( self.type_ignores: dict[int, list[str]] = {} # Cache of visit_X methods keyed by type of visited object - self.visitor_cache: dict[type, Callable[[AST | None], Any]] = {} + self.visitor_cache: dict[type[object], Callable[[AST | None], Any]] = {} def note(self, msg: str, line: int, column: int) -> None: self.errors.report(line, column, msg, severity="note", code=codes.SYNTAX) diff --git a/mypy/messages.py b/mypy/messages.py index adf4ba7dca78..b76781f3166a 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2068,7 +2068,7 @@ def report_protocol_problems( # note: method, attr MAX_ITEMS = 2 # Maximum number of conflicts, missing members, and overloads shown # List of special situations where we don't want to report additional problems - exclusions: dict[type[Any], list[str]] = { + exclusions: dict[type[object], list[str]] = { TypedDictType: ["typing.Mapping"], TupleType: ["typing.Iterable", "typing.Sequence"], } diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index fa795d1ffebd..ddfdaa8cb361 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -707,7 +707,7 @@ def get_base_types(self, obj: type) -> list[str]: # remove the class itself all_bases = all_bases[1:] # Remove base classes of other bases as redundant. - bases: list[type[Any]] = [] + bases: list[type[object]] = [] for base in all_bases: if not any(issubclass(b, base) for b in bases): bases.append(base) From fe0cd0a5b6deae9aad25cf8b0bc2e929c1ccfeaa Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Wed, 15 Nov 2023 22:43:30 +0100 Subject: [PATCH 03/21] Find other places where `analyze_annotation` should eventually be set to true. --- mypy/semanal.py | 6 +++-- mypy/typeanal.py | 1 + test-data/unit/check-callable.test | 11 --------- test-data/unit/check-newsemanal.test | 37 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1e19037941bb..945306e35f69 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5375,6 +5375,7 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None: allow_placeholder=True, allow_param_spec_literals=has_param_spec, allow_unpack=allow_unpack, + analyze_annotation=True, ) if analyzed is None: return None @@ -5399,7 +5400,7 @@ def visit_slice_expr(self, expr: SliceExpr) -> None: def visit_cast_expr(self, expr: CastExpr) -> None: expr.expr.accept(self) - analyzed = self.anal_type(expr.type) + analyzed = self.anal_type(expr.type, analyze_annotation=True) if analyzed is not None: expr.type = analyzed @@ -5421,7 +5422,7 @@ def visit_reveal_expr(self, expr: RevealExpr) -> None: def visit_type_application(self, expr: TypeApplication) -> None: expr.expr.accept(self) for i in range(len(expr.types)): - analyzed = self.anal_type(expr.types[i]) + analyzed = self.anal_type(expr.types[i], analyze_annotation=True) if analyzed is not None: expr.types[i] = analyzed @@ -6579,6 +6580,7 @@ def expr_to_analyzed_type( allow_unbound_tvars=allow_unbound_tvars, allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, + analyze_annotation=isinstance(expr, IndexExpr), ) def analyze_type_expr(self, expr: Expression) -> None: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 4ea315ea4ba2..48bfd0938da8 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -150,6 +150,7 @@ def analyze_type_alias( allow_placeholder=allow_placeholder, prohibit_self_type="type alias target", allowed_alias_tvars=allowed_alias_tvars, + analyze_annotation=True, ) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 8a611a689be5..4c7d8099039e 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -524,17 +524,6 @@ else: [builtins fixtures/callable.pyi] -[case testBuiltinsTypeAsCallable] -# flags: --python-version 3.7 -from __future__ import annotations - -reveal_type(type) # N: Revealed type is "def (x: Any) -> builtins.type" -_TYPE = type -reveal_type(_TYPE) # N: Revealed type is "def (x: Any) -> builtins.type" -_TYPE('bar') - -[builtins fixtures/callable.pyi] - [case testErrorMessageAboutSelf] # https://github.com/python/mypy/issues/11309 class Some: diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index ff8d346e74a1..751aeb29c2dc 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3235,3 +3235,40 @@ class b: x = x[1] # E: Cannot resolve name "x" (possible cyclic definition) y = 1[y] # E: Value of type "int" is not indexable \ # E: Cannot determine type of "y" + +[case testBuiltinTypeType] +# flags: --python-version 3.10 --disallow-any-generics + +from typing import Any, cast, Union +from typing_extensions import TypeAlias + +a: type # E: Missing type parameters for generic type "type" +reveal_type(a) # N: Revealed type is "Type[Any]" + +b: Any +c = cast(type, b) # E: Missing type parameters for generic type "type" +reveal_type(c) # N: Revealed type is "Type[Any]" + +d: list[type] # E: Missing type parameters for generic type "type" +reveal_type(d) # N: Revealed type is "builtins.list[Type[Any]]" + +class E(list[type]): ... # E: Missing type parameters for generic type "type" +reveal_type(E()[0]) # N: Revealed type is "Type[Any]" + +class F(tuple[type]): ... # E: Missing type parameters for generic type "type" +reveal_type(F()[0]) # N: Revealed type is "Type[Any]" + +class G(tuple[list[Union[type, int]], ...]): ... # E: Missing type parameters for generic type "type" +reveal_type(G()[0]) # N: Revealed type is "builtins.list[Union[Type[Any], builtins.int]]" + +h: Union[type, int] # E: Missing type parameters for generic type "type" +reveal_type(h) # N: Revealed type is "Union[Type[Any], builtins.int]" + +i: type | int # E: Missing type parameters for generic type "type" +reveal_type(i) # N: Revealed type is "Union[Type[Any], builtins.int]" + +j: TypeAlias = type # E: Missing type parameters for generic type "type" +k: j +reveal_type(k) # N: Revealed type is "Type[Any]" + +[builtins fixtures/tuple.pyi] From 28ee26b5551269f6fe0018b5f9a2b773f94d4f70 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Tue, 21 Nov 2023 06:21:01 +0100 Subject: [PATCH 04/21] * Rename `analyze_annotation` to `builtin_type_is_type_type` and make it true by default to cover more cases * Add the `BUILTIN_TYPE_USED_AS_GENERIC` error message * Complete the "testBuiltinTypeType" test case * Adjust other test cases * Adjust two stub files * Replace additional `type` annotations with `type[object]` in Mypy's code --- mypy/message_registry.py | 4 +++ mypy/messages.py | 6 ++++ mypy/semanal.py | 53 ++++++++++++++++++---------- mypy/stubgenc.py | 6 ++-- mypy/stubutil.py | 2 +- mypy/test/helpers.py | 4 +-- mypy/typeanal.py | 9 ++--- test-data/unit/check-classes.test | 9 +++-- test-data/unit/check-generics.test | 2 +- test-data/unit/check-isinstance.test | 2 +- test-data/unit/check-literal.test | 4 +-- test-data/unit/check-newsemanal.test | 37 +++++++++++++++++-- test-data/unit/fixtures/dict.pyi | 14 ++++++-- test-data/unit/fixtures/tuple.pyi | 15 ++++++-- 14 files changed, 123 insertions(+), 44 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index dc46eb503390..000ce5969864 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -175,6 +175,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage: IMPLICIT_GENERIC_ANY_BUILTIN: Final = ( 'Implicit generic "Any". Use "{}" and specify generic parameters' ) +BUILTIN_TYPE_USED_AS_GENERIC = ( + '"builtins.type" is indexable as a type hint but neither a generic class nor a generic ' + 'function' +) INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)" INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position" diff --git a/mypy/messages.py b/mypy/messages.py index b76781f3166a..c1246c838d20 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -417,6 +417,12 @@ def has_no_attr( # Indexed get. # TODO: Fix this consistently in format_type if isinstance(original_type, FunctionLike) and original_type.is_type_obj(): + if isinstance(original_type, CallableType) and isinstance( + ret_type := get_proper_type(original_type.ret_type), Instance) and ( + ret_type.type.fullname == "builtins.type" + ): + self.fail(message_registry.BUILTIN_TYPE_USED_AS_GENERIC, context) + return None self.fail( "The type {} is not generic and not indexable".format( format_type(original_type, self.options) diff --git a/mypy/semanal.py b/mypy/semanal.py index 945306e35f69..959981bfa5c4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2165,7 +2165,10 @@ def analyze_base_classes( try: base = self.expr_to_analyzed_type( - base_expr, allow_placeholder=True, allow_type_any=True + base_expr, + allow_placeholder=True, + allow_type_any=True, + builtin_type_is_type_type=not refers_to_fullname(base_expr, "builtins.type"), ) except TypeTranslationError: name = self.get_name_repr_of_expr(base_expr) @@ -2215,10 +2218,14 @@ def configure_base_classes( elif isinstance(base, TypedDictType): base_types.append(base.fallback) else: - msg = "Invalid base class" - name = self.get_name_repr_of_expr(base_expr) - if name: - msg += f' "{name}"' + if isinstance(base_expr, IndexExpr) and refers_to_fullname( + base_expr.base, "builtins.type" + ): + msg = message_registry.BUILTIN_TYPE_USED_AS_GENERIC + else: + msg = "Invalid base class" + if name := self.get_name_repr_of_expr(base_expr): + msg += f' "{name}"' self.fail(msg, base_expr) info.fallback_to_any = True if self.options.disallow_any_unimported and has_any_from_unimported_type(base): @@ -3384,9 +3391,7 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: if s.type: lvalue = s.lvalues[-1] allow_tuple_literal = isinstance(lvalue, TupleExpr) - analyzed = self.anal_type( - s.type, allow_tuple_literal=allow_tuple_literal, analyze_annotation=True - ) + analyzed = self.anal_type(s.type, allow_tuple_literal=allow_tuple_literal) # Don't store not ready types (including placeholders). if analyzed is None or has_placeholder(analyzed): self.defer(s) @@ -3464,7 +3469,11 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ return typ def analyze_alias( - self, name: str, rvalue: Expression, allow_placeholder: bool = False + self, + name: str, + rvalue: Expression, + allow_placeholder: bool = False, + builtin_type_is_type_type: bool = False, ) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]: """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable). @@ -3505,6 +3514,7 @@ def analyze_alias( in_dynamic_func=dynamic, global_scope=global_scope, allowed_alias_tvars=tvar_defs, + builtin_type_is_type_type=builtin_type_is_type_type, ) # There can be only one variadic variable at most, the error is reported elsewhere. @@ -3606,7 +3616,12 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: else: tag = self.track_incomplete_refs() res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias( - lvalue.name, rvalue, allow_placeholder=True + lvalue.name, + rvalue, + allow_placeholder=True, + builtin_type_is_type_type=( + s.type or not refers_to_fullname(s.rvalue, "builtins.type") + ), ) if not res: return False @@ -5375,7 +5390,6 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None: allow_placeholder=True, allow_param_spec_literals=has_param_spec, allow_unpack=allow_unpack, - analyze_annotation=True, ) if analyzed is None: return None @@ -5400,7 +5414,7 @@ def visit_slice_expr(self, expr: SliceExpr) -> None: def visit_cast_expr(self, expr: CastExpr) -> None: expr.expr.accept(self) - analyzed = self.anal_type(expr.type, analyze_annotation=True) + analyzed = self.anal_type(expr.type) if analyzed is not None: expr.type = analyzed @@ -5422,7 +5436,7 @@ def visit_reveal_expr(self, expr: RevealExpr) -> None: def visit_type_application(self, expr: TypeApplication) -> None: expr.expr.accept(self) for i in range(len(expr.types)): - analyzed = self.anal_type(expr.types[i], analyze_annotation=True) + analyzed = self.anal_type(expr.types[i]) if analyzed is not None: expr.types[i] = analyzed @@ -6549,6 +6563,7 @@ def expr_to_analyzed_type( allow_unbound_tvars: bool = False, allow_param_spec_literals: bool = False, allow_unpack: bool = False, + builtin_type_is_type_type: bool = True, ) -> Type | None: if isinstance(expr, CallExpr): # This is a legacy syntax intended mostly for Python 2, we keep it for @@ -6580,7 +6595,9 @@ def expr_to_analyzed_type( allow_unbound_tvars=allow_unbound_tvars, allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, - analyze_annotation=isinstance(expr, IndexExpr), + builtin_type_is_type_type=( + builtin_type_is_type_type or not refers_to_fullname(expr, "builtins.type") + ) ) def analyze_type_expr(self, expr: Expression) -> None: @@ -6606,7 +6623,7 @@ def type_analyzer( report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, - analyze_annotation: bool = False, + builtin_type_is_type_type: bool = True, ) -> TypeAnalyser: if tvar_scope is None: tvar_scope = self.tvar_scope @@ -6625,7 +6642,7 @@ def type_analyzer( allow_unpack=allow_unpack, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, - analyze_annotation=analyze_annotation, + builtin_type_is_type_type=builtin_type_is_type_type, ) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack @@ -6650,7 +6667,7 @@ def anal_type( report_invalid_types: bool = True, prohibit_self_type: str | None = None, allow_type_any: bool = False, - analyze_annotation: bool = False, + builtin_type_is_type_type: bool = True, third_pass: bool = False, ) -> Type | None: """Semantically analyze a type. @@ -6689,7 +6706,7 @@ def anal_type( report_invalid_types=report_invalid_types, prohibit_self_type=prohibit_self_type, allow_type_any=allow_type_any, - analyze_annotation=analyze_annotation, + builtin_type_is_type_type=builtin_type_is_type_type, ) tag = self.track_incomplete_refs() typ = typ.accept(a) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index ddfdaa8cb361..57d5f5f8bbed 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -684,7 +684,7 @@ def generate_property_stub( rw_properties.append(f"{self._indent}{name}: {inferred_type}") - def get_type_fullname(self, typ: type) -> str: + def get_type_fullname(self, typ: type[object]) -> str: """Given a type, return a string representation""" if typ is Any: return "Any" @@ -695,7 +695,7 @@ def get_type_fullname(self, typ: type) -> str: typename = f"{module_name}.{typename}" return typename - def get_base_types(self, obj: type) -> list[str]: + def get_base_types(self, obj: type[object]) -> list[str]: all_bases = type.mro(obj) if all_bases[-1] is object: # TODO: Is this always object? @@ -713,7 +713,7 @@ def get_base_types(self, obj: type) -> list[str]: bases.append(base) return [self.strip_or_import(self.get_type_fullname(base)) for base in bases] - def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> None: + def generate_class_stub(self, class_name: str, cls: type[object], output: list[str]) -> None: """Generate stub for a single class using runtime introspection. The result lines will be appended to 'output'. If necessary, any diff --git a/mypy/stubutil.py b/mypy/stubutil.py index cc3b63098fd2..192d6dccc940 100644 --- a/mypy/stubutil.py +++ b/mypy/stubutil.py @@ -277,7 +277,7 @@ def args_str(self, args: Iterable[Type]) -> str: class ClassInfo: def __init__( - self, name: str, self_var: str, docstring: str | None = None, cls: type | None = None + self, name: str, self_var: str, docstring: str | None = None, cls: type[object] | None = None ) -> None: self.name = name self.self_var = self_var diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index dc34931427ec..4a7b21bb7cbc 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -318,14 +318,14 @@ def assert_equal(a: object, b: object, fmt: str = "{} != {}") -> None: raise AssertionError(fmt.format(good_repr(a), good_repr(b))) -def typename(t: type) -> str: +def typename(t: type[object]) -> str: if "." in str(t): return str(t).split(".")[-1].rstrip("'>") else: return str(t)[8:-2] -def assert_type(typ: type, value: object) -> None: +def assert_type(typ: type[object], value: object) -> None: __tracebackhide__ = True if type(value) != typ: raise AssertionError(f"Invalid type {typename(type(value))}, expected {typename(typ)}") diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 48bfd0938da8..780cea209e8f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -133,6 +133,7 @@ def analyze_type_alias( in_dynamic_func: bool = False, global_scope: bool = True, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + builtin_type_is_type_type: bool = True, ) -> tuple[Type, set[str]]: """Analyze r.h.s. of a (potential) type alias definition. @@ -150,7 +151,7 @@ def analyze_type_alias( allow_placeholder=allow_placeholder, prohibit_self_type="type alias target", allowed_alias_tvars=allowed_alias_tvars, - analyze_annotation=True, + builtin_type_is_type_type=builtin_type_is_type_type, ) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope @@ -203,7 +204,7 @@ def __init__( prohibit_self_type: str | None = None, allowed_alias_tvars: list[TypeVarLikeType] | None = None, allow_type_any: bool = False, - analyze_annotation: bool = False, + builtin_type_is_type_type: bool = True, ) -> None: self.api = api self.fail_func = api.fail @@ -248,7 +249,7 @@ def __init__( self.allow_type_any = allow_type_any self.allow_type_var_tuple = False self.allow_unpack = allow_unpack - self.analyze_annotation = analyze_annotation + self.builtin_type_is_type_type = builtin_type_is_type_type def lookup_qualified( self, name: str, ctx: Context, suppress_errors: bool = False @@ -592,7 +593,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ ): if len(t.args) == 0: if fullname == "typing.Type" or ( - self.analyze_annotation and (fullname == "builtins.type") + self.builtin_type_is_type_type and (fullname == "builtins.type") ): any_type = self.get_omitted_any(t) return TypeType(any_type, line=t.line, column=t.column) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index cd60ec7c9a9c..f5d441d71515 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2136,7 +2136,7 @@ tmp/foo.pyi:8: note: def __add__(self, int, /) -> A tmp/foo.pyi:8: note: @overload tmp/foo.pyi:8: note: def __add__(self, str, /) -> A tmp/foo.pyi:8: note: @overload -tmp/foo.pyi:8: note: def __add__(self, type, /) -> A +tmp/foo.pyi:8: note: def __add__(self, Type[Any], /) -> A tmp/foo.pyi:8: note: Overloaded operator methods can't have wider argument types in overrides [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] @@ -3431,7 +3431,7 @@ def foo(arg: Type[Any]): from typing import Type, Any def foo(arg: Type[Any]): reveal_type(arg.__str__) # N: Revealed type is "def () -> builtins.str" - reveal_type(arg.mro()) # N: Revealed type is "builtins.list[builtins.type[Any]]" + reveal_type(arg.mro()) # N: Revealed type is "builtins.list[Type[Any]]" [builtins fixtures/type.pyi] [out] @@ -3771,7 +3771,7 @@ def f(a: type) -> None: pass f(3) # E: No overload variant of "f" matches argument type "int" \ # N: Possible overload variants: \ # N: def f(a: Type[User]) -> None \ - # N: def f(a: type) -> None + # N: def f(a: Type[Any]) -> None [builtins fixtures/classmethod.pyi] [out] @@ -5490,8 +5490,7 @@ def f() -> type: return M class C1(six.with_metaclass(M), object): pass # E: Unsupported dynamic base class "six.with_metaclass" class C2(C1, six.with_metaclass(M)): pass # E: Unsupported dynamic base class "six.with_metaclass" class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from "type" are not supported -@six.add_metaclass(A) # E: Metaclasses not inheriting from "type" are not supported \ - # E: Argument 1 to "add_metaclass" has incompatible type "Type[A]"; expected "Type[type]" +@six.add_metaclass(A) # E: Metaclasses not inheriting from "type" are not supported class D3(A): pass class C4(six.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 0781451e07ce..108b1ae98f67 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -513,7 +513,7 @@ Alias[int]("a") # E: Argument 1 to "Node" has incompatible type "str"; expected [out] [case testTypeApplicationCrash] -type[int] # this was crashing, see #2302 (comment) # E: The type "Type[type]" is not generic and not indexable +type[int] # this was crashing, see #2302 (comment) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function [out] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b7ee38b69d00..fb54cdb3b6fc 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1883,7 +1883,7 @@ if issubclass(y): # E: Missing positional argument "t" in call to "issubclass" [case testIsInstanceTooManyArgs] isinstance(1, 1, 1) # E: Too many arguments for "isinstance" \ - # E: Argument 2 to "isinstance" has incompatible type "int"; expected "Union[type, Tuple[Any, ...]]" + # E: Argument 2 to "isinstance" has incompatible type "int"; expected "Union[Type[Any], Tuple[Any, ...]]" x: object if isinstance(x, str, 1): # E: Too many arguments for "isinstance" reveal_type(x) # N: Revealed type is "builtins.object" diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index d9ad68385ad1..42f32b4f8a41 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -1480,13 +1480,13 @@ Alias = Literal[3] isinstance(3, Literal[3]) # E: Cannot use isinstance() with Literal type isinstance(3, Alias) # E: Cannot use isinstance() with Literal type \ - # E: Argument 2 to "isinstance" has incompatible type ""; expected "Union[type, Tuple[Any, ...]]" + # E: Argument 2 to "isinstance" has incompatible type ""; expected "Union[Type[Any], Tuple[Any, ...]]" isinstance(3, Renamed[3]) # E: Cannot use isinstance() with Literal type isinstance(3, indirect.Literal[3]) # E: Cannot use isinstance() with Literal type issubclass(int, Literal[3]) # E: Cannot use issubclass() with Literal type issubclass(int, Alias) # E: Cannot use issubclass() with Literal type \ - # E: Argument 2 to "issubclass" has incompatible type ""; expected "Union[type, Tuple[Any, ...]]" + # E: Argument 2 to "issubclass" has incompatible type ""; expected "Union[Type[Any], Tuple[Any, ...]]" issubclass(int, Renamed[3]) # E: Cannot use issubclass() with Literal type issubclass(int, indirect.Literal[3]) # E: Cannot use issubclass() with Literal type [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 751aeb29c2dc..b647a6459939 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3209,6 +3209,25 @@ class User: def __init__(self, name: str) -> None: self.name = name # E: Cannot assign to a method + + + +[case testTempTemp] +# flags: --python-version 3.10 --disallow-any-generics + +from typing import Any, cast, Union, TypeVar +from typing_extensions import TypeAlias + +j: TypeAlias = type # E: Missing type parameters for generic type "type" +class K(metaclass=j): ... # E: Invalid metaclass "j" +m = type +class N(metaclass=m): ... + +[builtins fixtures/tuple.pyi] + + + + [case testNewAnalyzerMemberNameMatchesTypedDict] from typing import Union, Any from typing_extensions import TypedDict @@ -3239,7 +3258,7 @@ y = 1[y] # E: Value of type "int" is not indexable \ [case testBuiltinTypeType] # flags: --python-version 3.10 --disallow-any-generics -from typing import Any, cast, Union +from typing import Any, cast, Union, TypeVar from typing_extensions import TypeAlias a: type # E: Missing type parameters for generic type "type" @@ -3268,7 +3287,19 @@ i: type | int # E: Missing type parameters for generic type "type" reveal_type(i) # N: Revealed type is "Union[Type[Any], builtins.int]" j: TypeAlias = type # E: Missing type parameters for generic type "type" -k: j -reveal_type(k) # N: Revealed type is "Type[Any]" +class K(metaclass=j): ... # E: Invalid metaclass "j" +l: j +reveal_type(l) # N: Revealed type is "Type[Any]" + +m = type +class N(metaclass=m): ... + +O = TypeVar("O", bound=type) # E: Missing type parameters for generic type "type" +def p(q: O) -> O: ... +reveal_type(str) # N: Revealed type is "def () -> builtins.str" + +type[str](1) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function +r = type[str]("r", (), {}) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function +class S(type[str]): ... # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 19d175ff79ab..216b0ee23d12 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -3,7 +3,17 @@ from _typeshed import SupportsKeysAndGetItem import _typeshed from typing import ( - TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union, Sequence + Any, + Generic, + Iterable, + Iterator, + Mapping, + overload, + Optional, + Sequence, + Tuple, + TypeVar, + Union, ) T = TypeVar('T') @@ -60,7 +70,7 @@ class bool(int): pass class ellipsis: __class__: object -def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass +def isinstance(x: object, t: Union[type[Any], Tuple[type[Any], ...]]) -> bool: pass # type: ignore class BaseException: pass def iter(__iterable: Iterable[T]) -> Iterator[T]: pass diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index e270f3d79d3e..49c8fe92831e 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -1,7 +1,18 @@ # Builtins stub used in tuple-related test cases. import _typeshed -from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Type +from typing import ( + Any, + Iterable, + Iterator, + TypeVar, + Generic, + Sequence, + Optional, + overload, + Tuple, + Type, +) T = TypeVar("T") Tco = TypeVar('Tco', covariant=True) @@ -47,7 +58,7 @@ class list(Sequence[T], Generic[T]): def __contains__(self, item: object) -> bool: ... def __iter__(self) -> Iterator[T]: ... -def isinstance(x: object, t: type) -> bool: pass +def isinstance(x: object, t: type[Any]) -> bool: pass def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass From dcf1f951e808b9bd81ca177035af37095cf7c16e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 06:36:54 +0000 Subject: [PATCH 05/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/message_registry.py | 2 +- mypy/messages.py | 7 ++++--- mypy/semanal.py | 2 +- mypy/stubutil.py | 6 +++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index ab50a78f0fa0..bb72f3a5c7bf 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -180,7 +180,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: ) BUILTIN_TYPE_USED_AS_GENERIC = ( '"builtins.type" is indexable as a type hint but neither a generic class nor a generic ' - 'function' + "function" ) INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)" INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position" diff --git a/mypy/messages.py b/mypy/messages.py index ea50fabeef04..03c6638b5b40 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -417,9 +417,10 @@ def has_no_attr( # Indexed get. # TODO: Fix this consistently in format_type if isinstance(original_type, FunctionLike) and original_type.is_type_obj(): - if isinstance(original_type, CallableType) and isinstance( - ret_type := get_proper_type(original_type.ret_type), Instance) and ( - ret_type.type.fullname == "builtins.type" + if ( + isinstance(original_type, CallableType) + and isinstance(ret_type := get_proper_type(original_type.ret_type), Instance) + and (ret_type.type.fullname == "builtins.type") ): self.fail(message_registry.BUILTIN_TYPE_USED_AS_GENERIC, context) return None diff --git a/mypy/semanal.py b/mypy/semanal.py index 486038474394..efc3ca4bcc3d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -6599,7 +6599,7 @@ def expr_to_analyzed_type( allow_unpack=allow_unpack, builtin_type_is_type_type=( builtin_type_is_type_type or not refers_to_fullname(expr, "builtins.type") - ) + ), ) def analyze_type_expr(self, expr: Expression) -> None: diff --git a/mypy/stubutil.py b/mypy/stubutil.py index ced05b8f9c6d..49c49a2eb181 100644 --- a/mypy/stubutil.py +++ b/mypy/stubutil.py @@ -277,7 +277,11 @@ def args_str(self, args: Iterable[Type]) -> str: class ClassInfo: def __init__( - self, name: str, self_var: str, docstring: str | None = None, cls: type[object] | None = None + self, + name: str, + self_var: str, + docstring: str | None = None, + cls: type[object] | None = None, ) -> None: self.name = name self.self_var = self_var From b1bb26c9019ed2d45d2e1bfb407dd0bdb64dfe4f Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Tue, 21 Nov 2023 07:46:55 +0100 Subject: [PATCH 06/21] small fixes --- mypy/nodes.py | 2 +- mypy/semanal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 17e06613d1e3..0522edfff199 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3400,7 +3400,7 @@ class FakeInfo(TypeInfo): def __init__(self, msg: str) -> None: self.msg = msg - def __getattribute__(self, attr: str) -> type: + def __getattribute__(self, attr: str) -> type[object]: # Handle __class__ so that isinstance still works... if attr == "__class__": return object.__getattribute__(self, attr) # type: ignore[no-any-return] diff --git a/mypy/semanal.py b/mypy/semanal.py index 486038474394..d180b98e2fac 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3625,7 +3625,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: rvalue, allow_placeholder=True, builtin_type_is_type_type=( - s.type or not refers_to_fullname(s.rvalue, "builtins.type") + (s.type is not None) or not refers_to_fullname(s.rvalue, "builtins.type") ), ) if not res: From 80a8c6792896ee52b30627a320c3fec884619708 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Tue, 21 Nov 2023 17:43:44 +0100 Subject: [PATCH 07/21] minor fixes after review --- test-data/unit/check-newsemanal.test | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 016bb4cfc9db..df9c8c4fb252 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3215,25 +3215,6 @@ class User: def __init__(self, name: str) -> None: self.name = name # E: Cannot assign to a method - - - -[case testTempTemp] -# flags: --python-version 3.10 --disallow-any-generics - -from typing import Any, cast, Union, TypeVar -from typing_extensions import TypeAlias - -j: TypeAlias = type # E: Missing type parameters for generic type "type" -class K(metaclass=j): ... # E: Invalid metaclass "j" -m = type -class N(metaclass=m): ... - -[builtins fixtures/tuple.pyi] - - - - [case testNewAnalyzerMemberNameMatchesTypedDict] from typing import Union, Any from typing_extensions import TypedDict @@ -3302,7 +3283,7 @@ class N(metaclass=m): ... O = TypeVar("O", bound=type) # E: Missing type parameters for generic type "type" def p(q: O) -> O: ... -reveal_type(str) # N: Revealed type is "def () -> builtins.str" +reveal_type(p(str)) # N: Revealed type is "def () -> builtins.str" type[str](1) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function r = type[str]("r", (), {}) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function From ab7322cbf1a49ca08a656dd2aa8145b93455e53f Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Tue, 21 Nov 2023 17:46:50 +0100 Subject: [PATCH 08/21] Add type[ignore] to tuple.pyi to satisfy testDisallowAnyExplicitGenericAlias which uses the disallow-any-explicit flag --- test-data/unit/fixtures/tuple.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 49c8fe92831e..28223e6037ba 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -58,7 +58,7 @@ class list(Sequence[T], Generic[T]): def __contains__(self, item: object) -> bool: ... def __iter__(self) -> Iterator[T]: ... -def isinstance(x: object, t: type[Any]) -> bool: pass +def isinstance(x: object, t: type[Any]) -> bool: pass # type: ignore def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass From aa0cd97edb5f94b5eaea0414f0f3540ae1be9ccb Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Tue, 21 Nov 2023 19:05:43 +0100 Subject: [PATCH 09/21] Add error codes to the type: ignores in tuple.pyi (and tuple.pyi) to satisfy testErrorCodeMissingMultiple --- test-data/unit/fixtures/dict.pyi | 2 +- test-data/unit/fixtures/tuple.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 8eef96c0401b..fbcfb8a89e17 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -87,7 +87,7 @@ class bool(int): pass class ellipsis: __class__: object -def isinstance(x: object, t: Union[type[Any], Tuple[type[Any], ...]]) -> bool: pass # type: ignore +def isinstance(x: object, t: Union[type[Any], Tuple[type[Any], ...]]) -> bool: pass # type: ignore[misc] class BaseException: pass def iter(__iterable: Iterable[T]) -> Iterator[T]: pass diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 28223e6037ba..0fb502592685 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -58,7 +58,7 @@ class list(Sequence[T], Generic[T]): def __contains__(self, item: object) -> bool: ... def __iter__(self) -> Iterator[T]: ... -def isinstance(x: object, t: type[Any]) -> bool: pass # type: ignore +def isinstance(x: object, t: type[Any]) -> bool: pass # type: ignore[misc] def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass From dcd12ba73e050268b0afcdc4f39a1b89214826c1 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Fri, 24 Nov 2023 20:26:04 +0100 Subject: [PATCH 10/21] Consider TypeType as hashable if (if no metaclass sets __hash__ to None) --- mypy/subtypes.py | 9 ++++++++ test-data/unit/check-protocols.test | 32 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 4fd3f8ff98ca..725c0de53f9d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1055,6 +1055,15 @@ def visit_type_type(self, left: TypeType) -> bool: # of x is Type[int]. It's unclear what's the right way to address this. return True item = left.item + if right.type.fullname == "typing.Hashable": + if isinstance(item, AnyType): + return True + if isinstance(item, Instance): + return ( + (metaclass_type := item.type.metaclass_type) is None or + (symtab := metaclass_type.type.get("__hash__")) is None or + isinstance(symtab.node, FunctionLike) + ) if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) if isinstance(item, Instance): diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index e73add454a67..fa02766d2087 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -323,6 +323,38 @@ var: MyHashable = C() # E: Incompatible types in assignment (expression has typ # N: Following member(s) of "C" have conflicts: \ # N: __my_hash__: expected "Callable[[], int]", got "None" +[case testTypeTypeIsHashable] +# flags: --python-version 3.9 + +from typing import Any, Hashable + +def f(x: Hashable) -> None: ... + +def g0(x: type[Any]) -> None: + f(x) + +class C1: ... +def g1(x: type[C1]) -> None: + f(x) + +class C2(metaclass=type): ... +def g2(x: type[C2]) -> None: + f(x) + +class M1(type): ... +class C3(metaclass=M1): ... +def g3(x: type[C3]) -> None: + f(x) + +class M2(M1): + __hash__ = None +class C4(metaclass=M2): ... +def g4(x: type[C4]) -> None: + f(x) # E: Argument 1 to "f" has incompatible type "Type[C4]"; expected "Hashable" + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + [case testNoneDisablesProtocolSubclassingWithStrictOptional] from typing import Protocol From e25f8e82ac7803a59ff2787aeebad286e681701b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 19:28:34 +0000 Subject: [PATCH 11/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/subtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 725c0de53f9d..a0bb311dd840 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1060,9 +1060,9 @@ def visit_type_type(self, left: TypeType) -> bool: return True if isinstance(item, Instance): return ( - (metaclass_type := item.type.metaclass_type) is None or - (symtab := metaclass_type.type.get("__hash__")) is None or - isinstance(symtab.node, FunctionLike) + (metaclass_type := item.type.metaclass_type) is None + or (symtab := metaclass_type.type.get("__hash__")) is None + or isinstance(symtab.node, FunctionLike) ) if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) From ed0f65255c8d54438fd1693fc65c703de5b55f1f Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Fri, 24 Nov 2023 22:38:25 +0100 Subject: [PATCH 12/21] Look for __hash__ instead of checking the name of the potential Hashable --- mypy/subtypes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a0bb311dd840..1f573e1d8955 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1055,15 +1055,18 @@ def visit_type_type(self, left: TypeType) -> bool: # of x is Type[int]. It's unclear what's the right way to address this. return True item = left.item - if right.type.fullname == "typing.Hashable": + if right.type.is_protocol and ( + len(right.type.protocol_members) == 1) and ( + right.type.protocol_members[0] == "__hash__"): if isinstance(item, AnyType): return True if isinstance(item, Instance): - return ( + if ( (metaclass_type := item.type.metaclass_type) is None or (symtab := metaclass_type.type.get("__hash__")) is None or isinstance(symtab.node, FunctionLike) - ) + ): + return True if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) if isinstance(item, Instance): From a46e5d97433645c67da124ee248bb2009a6f7081 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 21:39:00 +0000 Subject: [PATCH 13/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/subtypes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 1f573e1d8955..3ea3586221b8 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1055,9 +1055,11 @@ def visit_type_type(self, left: TypeType) -> bool: # of x is Type[int]. It's unclear what's the right way to address this. return True item = left.item - if right.type.is_protocol and ( - len(right.type.protocol_members) == 1) and ( - right.type.protocol_members[0] == "__hash__"): + if ( + right.type.is_protocol + and (len(right.type.protocol_members) == 1) + and (right.type.protocol_members[0] == "__hash__") + ): if isinstance(item, AnyType): return True if isinstance(item, Instance): From 7d1d3f23067007093c65e1fa85d360b76518d858 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 26 Nov 2023 17:55:40 +0100 Subject: [PATCH 14/21] Add another `TypeVar`-related test to `testBuiltinTypeType`. --- test-data/unit/check-newsemanal.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index df9c8c4fb252..0282f2237b33 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3284,6 +3284,7 @@ class N(metaclass=m): ... O = TypeVar("O", bound=type) # E: Missing type parameters for generic type "type" def p(q: O) -> O: ... reveal_type(p(str)) # N: Revealed type is "def () -> builtins.str" +p(1) # E: Value of type variable "O" of "p" cannot be "int" type[str](1) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function r = type[str]("r", (), {}) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function From 7107fb86166dc18e73dd726d8808937e894eb32b Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Mon, 27 Nov 2023 06:02:13 +0100 Subject: [PATCH 15/21] Add more test cases to `testBuiltinTypeType`. --- test-data/unit/check-protocols.test | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index fa02766d2087..12710db85d53 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -352,6 +352,18 @@ class C4(metaclass=M2): ... def g4(x: type[C4]) -> None: f(x) # E: Argument 1 to "f" has incompatible type "Type[C4]"; expected "Hashable" +class M3(M1): + def __hash__(self) -> None: ... +class C5(metaclass=M3): ... +def g5(x: type[C5]) -> None: + f(x) # E: Argument 1 to "f" has incompatible type "Type[C5]"; expected "Hashable" + +class M4(M3): + def __hash__(self) -> int: ... # type: ignore +class C6(metaclass=M4): ... +def g6(x: type[C6]) -> None: + f(x) + [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] From 385892859f0632f292b212fcffa603408268ebd2 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Mon, 27 Nov 2023 06:03:33 +0100 Subject: [PATCH 16/21] Check more thoroughly if the `Protocol` is `Hashable`-like. --- mypy/subtypes.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 3ea3586221b8..c90521b8666d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1057,8 +1057,14 @@ def visit_type_type(self, left: TypeType) -> bool: item = left.item if ( right.type.is_protocol - and (len(right.type.protocol_members) == 1) - and (right.type.protocol_members[0] == "__hash__") + and len(right.type.protocol_members) == 1 + and right.type.protocol_members[0] == "__hash__" + and (symtab := right.type.get("__hash__")) is not None + and isinstance(hash_ := get_proper_type(symtab.type), CallableType) + and len(hash_.arg_names) == 1 + and hash_.arg_names[0] == "self" + and isinstance(ret := get_proper_type(hash_.ret_type), Instance) + and ret.type.fullname == "builtins.int" ): if isinstance(item, AnyType): return True From a09c6e2a1579de18ff7bc522795ea3f8dd243044 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Mon, 27 Nov 2023 06:04:30 +0100 Subject: [PATCH 17/21] Check more thoroughly if the signatures agree. --- mypy/subtypes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c90521b8666d..7975c5b8b27f 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1070,11 +1070,16 @@ def visit_type_type(self, left: TypeType) -> bool: return True if isinstance(item, Instance): if ( - (metaclass_type := item.type.metaclass_type) is None - or (symtab := metaclass_type.type.get("__hash__")) is None - or isinstance(symtab.node, FunctionLike) + (mtype := item.type.metaclass_type) is None + or (symtab := mtype.type.get("__hash__")) is None ): return True + supertype = get_proper_type(find_member("__hash__", right, mtype)) + assert supertype is not None + subtype = mypy.typeops.get_protocol_member(mtype, "__hash__", False) + assert subtype is not None + if is_subtype(subtype, supertype, ignore_pos_arg_names=True): + return True if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) if isinstance(item, Instance): From 1a2202f242d95eb4ba73bcd47cdeb2013747d774 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 05:05:12 +0000 Subject: [PATCH 18/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/subtypes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 7975c5b8b27f..57506ea8bfe1 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1069,10 +1069,9 @@ def visit_type_type(self, left: TypeType) -> bool: if isinstance(item, AnyType): return True if isinstance(item, Instance): - if ( - (mtype := item.type.metaclass_type) is None - or (symtab := mtype.type.get("__hash__")) is None - ): + if (mtype := item.type.metaclass_type) is None or ( + symtab := mtype.type.get("__hash__") + ) is None: return True supertype = get_proper_type(find_member("__hash__", right, mtype)) assert supertype is not None From 4330ea07d4897f13bdd4d11366c25f5ffbaf7b16 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Mon, 11 Dec 2023 05:32:46 +0100 Subject: [PATCH 19/21] remove unused local symtab variable --- mypy/subtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 57506ea8bfe1..7f68abf4f58d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1070,8 +1070,8 @@ def visit_type_type(self, left: TypeType) -> bool: return True if isinstance(item, Instance): if (mtype := item.type.metaclass_type) is None or ( - symtab := mtype.type.get("__hash__") - ) is None: + mtype.type.get("__hash__") is None + ): return True supertype = get_proper_type(find_member("__hash__", right, mtype)) assert supertype is not None From c7dbe6ac8e90c993fee63783c62a5b9c7618bc67 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sat, 2 Nov 2024 22:02:42 -0700 Subject: [PATCH 20/21] fix merge --- test-data/unit/check-lowercase.test | 2 +- test-data/unit/check-newsemanal.test | 5 +++-- test-data/unit/fixtures/isinstance.pyi | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-lowercase.test b/test-data/unit/check-lowercase.test index 7090493e4854..17daa478b1d6 100644 --- a/test-data/unit/check-lowercase.test +++ b/test-data/unit/check-lowercase.test @@ -48,7 +48,7 @@ x = 3 # E: Incompatible types in assignment (expression has type "int", variabl x: type[type] # E: Type[...] can't contain another Type[...] y: int -y = x # E: Incompatible types in assignment (expression has type "type[type]", variable has type "int") +y = x # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "int") [case testLowercaseSettingOnTypeAnnotationHint] # flags: --python-version 3.9 --no-force-uppercase-builtins diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 6164090b1ae9..fb4607f24465 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -3244,6 +3244,7 @@ y = 1[y] # E: Value of type "int" is not indexable \ [case testBuiltinTypeType] # flags: --python-version 3.10 --disallow-any-generics +import types from typing import Any, cast, Union, TypeVar from typing_extensions import TypeAlias @@ -3285,8 +3286,8 @@ def p(q: O) -> O: ... reveal_type(p(str)) # N: Revealed type is "def () -> builtins.str" p(1) # E: Value of type variable "O" of "p" cannot be "int" -type[str](1) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function -r = type[str]("r", (), {}) # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function +type[str](1) # E: "GenericAlias" not callable +r = type[str]("r", (), {}) # E: "GenericAlias" not callable class S(type[str]): ... # E: "builtins.type" is indexable as a type hint but neither a generic class nor a generic function [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fixtures/isinstance.pyi b/test-data/unit/fixtures/isinstance.pyi index 12cef2035c2b..ae75fb204faf 100644 --- a/test-data/unit/fixtures/isinstance.pyi +++ b/test-data/unit/fixtures/isinstance.pyi @@ -7,7 +7,7 @@ class object: class type: def __init__(self, x) -> None: pass - def __or__(self, other: type) -> type: pass + def __or__(self, other: type[Any]) -> type[Any]: pass class tuple(Generic[T]): pass From 5a3e028580d03e5baf00a31aa488100acb11c110 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2024 05:07:20 +0000 Subject: [PATCH 21/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/stubgenc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index af1275c4dc5c..e957770b9be2 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -788,7 +788,11 @@ def get_base_types(self, obj: type[object]) -> list[str]: return [self.strip_or_import(self.get_type_fullname(base)) for base in bases] def generate_class_stub( - self, class_name: str, cls: type[object], output: list[str], parent_class: ClassInfo | None = None + self, + class_name: str, + cls: type[object], + output: list[str], + parent_class: ClassInfo | None = None, ) -> None: """Generate stub for a single class using runtime introspection.