From 4775da1977dc116d2005e61e819df1603780178a Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 18 Oct 2024 13:59:18 -0700 Subject: [PATCH 1/7] Bump version to 1.12.1+dev --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index 57ee134ff8b8..7a022f8d3ce3 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.12.0" +__version__ = "1.12.1+dev" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) From a5e9b0b7fe5f2b5e3b3efac844216159a24ebd9c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 16 Oct 2024 15:36:09 +0100 Subject: [PATCH 2/7] Fix crash when showing partially analyzed type in error message (#17961) Fixes https://github.com/python/mypy/issues/17954 People say something about cache invalidation being one of the hardest problems... --- mypy/semanal.py | 8 ++++++-- test-data/unit/check-recursive-types.test | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 27abf2c1dc4c..00383c010dc9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3958,8 +3958,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # so we need to replace it with non-explicit Anys. res = make_any_non_explicit(res) if self.options.disallow_any_unimported and has_any_from_unimported_type(res): - self.msg.unimported_type_becomes_any("Type alias target", res, s) - res = make_any_non_unimported(res) + # Only show error message once, when the type is fully analyzed. + if not has_placeholder(res): + self.msg.unimported_type_becomes_any("Type alias target", res, s) + res = make_any_non_unimported(res) # Note: with the new (lazy) type alias representation we only need to set no_args to True # if the expected number of arguments is non-zero, so that aliases like `A = List` work # but not aliases like `A = TypeAliasType("A", List)` as these need explicit type params. @@ -4013,6 +4015,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: existing.node.alias_tvars = alias_tvars existing.node.no_args = no_args updated = True + # Invalidate recursive status cache in case it was previously set. + existing.node._is_recursive = None else: # Otherwise just replace existing placeholder with type alias. existing.node = alias_node diff --git a/test-data/unit/check-recursive-types.test b/test-data/unit/check-recursive-types.test index ac1ea0c0035a..4d7af98204fb 100644 --- a/test-data/unit/check-recursive-types.test +++ b/test-data/unit/check-recursive-types.test @@ -1006,3 +1006,11 @@ ta: Tuple[A] p: Proto p = ta [builtins fixtures/tuple.pyi] + +[case testRecursiveAliasesWithAnyUnimported] +# flags: --disallow-any-unimported +from typing import Callable +from bogus import Foo # type: ignore + +A = Callable[[Foo, "B"], Foo] # E: Type alias target becomes "Callable[[Any, B], Any]" due to an unfollowed import +B = Callable[[Foo, A], Foo] # E: Type alias target becomes "Callable[[Any, A], Any]" due to an unfollowed import From 2485bed372cbc82ea4789c480eb6359905693e86 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:12:22 -0700 Subject: [PATCH 3/7] Use kw-only args for member access booleans (#17975) Gets hard to know what's what at call site --- mypy/checker.py | 8 ++++---- mypy/checkexpr.py | 32 ++++++++++++++++---------------- mypy/checkmember.py | 35 ++++++++++++++++++----------------- mypy/checkpattern.py | 16 ++++++++-------- 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e95bd36174ab..2a11e6bde82f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7520,10 +7520,10 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool: name, typ, TempNode(AnyType(TypeOfAny.special_form)), - False, - False, - False, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=False, + msg=self.msg, original_type=typ, chk=self, # This is not a real attribute lookup so don't mess with deferring nodes. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 98e6eb6a7fc3..770ba4bd352f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1497,10 +1497,10 @@ def check_union_call_expr(self, e: CallExpr, object_type: UnionType, member: str member, typ, e, - False, - False, - False, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=False, + msg=self.msg, original_type=object_type, chk=self.chk, in_literal_context=self.is_literal_context(), @@ -3293,10 +3293,10 @@ def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type e.name, original_type, e, - is_lvalue, - False, - False, - self.msg, + is_lvalue=is_lvalue, + is_super=False, + is_operator=False, + msg=self.msg, original_type=original_type, chk=self.chk, in_literal_context=self.is_literal_context(), @@ -3317,10 +3317,10 @@ def analyze_external_member_access( member, base_type, context, - False, - False, - False, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=False, + msg=self.msg, original_type=base_type, chk=self.chk, in_literal_context=self.is_literal_context(), @@ -3800,10 +3800,10 @@ def check_method_call_by_name( method, base_type, context, - False, - False, - True, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=True, + msg=self.msg, original_type=original_type, chk=self.chk, in_literal_context=self.is_literal_context(), diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 8f99f96e2dd5..f18c891248ff 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -87,6 +87,7 @@ class MemberContext: def __init__( self, + *, is_lvalue: bool, is_super: bool, is_operator: bool, @@ -126,16 +127,16 @@ def copy_modified( original_type: Type | None = None, ) -> MemberContext: mx = MemberContext( - self.is_lvalue, - self.is_super, - self.is_operator, - self.original_type, - self.context, - self.msg, - self.chk, - self.self_type, - self.module_symbol_table, - self.no_deferral, + is_lvalue=self.is_lvalue, + is_super=self.is_super, + is_operator=self.is_operator, + original_type=self.original_type, + context=self.context, + msg=self.msg, + chk=self.chk, + self_type=self.self_type, + module_symbol_table=self.module_symbol_table, + no_deferral=self.no_deferral, ) if messages is not None: mx.msg = messages @@ -152,11 +153,11 @@ def analyze_member_access( name: str, typ: Type, context: Context, + *, is_lvalue: bool, is_super: bool, is_operator: bool, msg: MessageBuilder, - *, original_type: Type, chk: mypy.checker.TypeChecker, override_info: TypeInfo | None = None, @@ -190,12 +191,12 @@ def analyze_member_access( are not available via the type object directly) """ mx = MemberContext( - is_lvalue, - is_super, - is_operator, - original_type, - context, - msg, + is_lvalue=is_lvalue, + is_super=is_super, + is_operator=is_operator, + original_type=original_type, + context=context, + msg=msg, chk=chk, self_type=self_type, module_symbol_table=module_symbol_table, diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index fa23dfb5f453..6b4fa35f9c49 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -594,10 +594,10 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: "__match_args__", typ, o, - False, - False, - False, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=False, + msg=self.msg, original_type=typ, chk=self.chk, ) @@ -660,10 +660,10 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType: keyword, narrowed_type, pattern, - False, - False, - False, - self.msg, + is_lvalue=False, + is_super=False, + is_operator=False, + msg=self.msg, original_type=new_type, chk=self.chk, ) From 34d86038bab0b79251019fd1cbb438b7aef1a592 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 17 Oct 2024 02:18:47 -0700 Subject: [PATCH 4/7] Fix iteration over union (when self type is involved) (#17976) Fixes https://github.com/python/mypy/issues/17945 --- mypy/checkexpr.py | 1 + test-data/unit/check-selftype.test | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 770ba4bd352f..ffd72900bb2e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3805,6 +3805,7 @@ def check_method_call_by_name( is_operator=True, msg=self.msg, original_type=original_type, + self_type=base_type, chk=self.chk, in_literal_context=self.is_literal_context(), ) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 9601852ef823..1a088fe05092 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2160,3 +2160,19 @@ class MyProtocol(Protocol): def test() -> None: ... value: MyProtocol = test + +[case testSelfTypeUnionIter] +from typing import Self, Iterator, Generic, TypeVar, Union + +T = TypeVar("T") + +class range(Generic[T]): + def __iter__(self) -> Self: ... + def __next__(self) -> T: ... + +class count: + def __iter__(self) -> Iterator[int]: ... + +def foo(x: Union[range[int], count]) -> None: + for item in x: + reveal_type(item) # N: Revealed type is "builtins.int" From 71e1f055f0fcfa289a8ed6e82297ceb0a3e74417 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 18 Oct 2024 11:44:58 +0100 Subject: [PATCH 5/7] Fix type object with type var default in union context (#17991) Union type context wasn't handled previously, and it triggered false positives, but apparently only if a type object had type var defaults. Fixes #17942. --- mypy/checkexpr.py | 13 +++++++++++-- test-data/unit/check-typevar-defaults.test | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index ffd72900bb2e..304cd8054e6f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -396,8 +396,8 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: # TODO: always do this in type_object_type by passing the original context result.ret_type.line = e.line result.ret_type.column = e.column - if isinstance(get_proper_type(self.type_context[-1]), TypeType): - # This is the type in a Type[] expression, so substitute type + if is_type_type_context(self.type_context[-1]): + # This is the type in a type[] expression, so substitute type # variables with Any. result = erasetype.erase_typevars(result) elif isinstance(node, MypyFile): @@ -6592,3 +6592,12 @@ def get_partial_instance_type(t: Type | None) -> PartialType | None: if t is None or not isinstance(t, PartialType) or t.type is None: return None return t + + +def is_type_type_context(context: Type | None) -> bool: + context = get_proper_type(context) + if isinstance(context, TypeType): + return True + if isinstance(context, UnionType): + return any(is_type_type_context(item) for item in context.items) + return False diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test index 9ca67376da26..22e2594eb38b 100644 --- a/test-data/unit/check-typevar-defaults.test +++ b/test-data/unit/check-typevar-defaults.test @@ -717,3 +717,15 @@ def func_d3( reveal_type(c) # N: Revealed type is "__main__.B[__main__.A[builtins.dict[builtins.int, builtins.float]]]" reveal_type(d) # N: Revealed type is "__main__.B[builtins.int]" [builtins fixtures/dict.pyi] + +[case testTypeVarDefaultsAndTypeObjectTypeInUnion] +from __future__ import annotations +from typing import Generic +from typing_extensions import TypeVar + +_I = TypeVar("_I", default=int) + +class C(Generic[_I]): pass + +t: type[C] | int = C +[builtins fixtures/tuple.pyi] From 346e370fc314b3c7c84a1e3e920dd9afe039b4bd Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:28:53 -0700 Subject: [PATCH 6/7] [1.12 backport] revert os.path change (#17995) --- mypy/typeshed/stdlib/posixpath.pyi | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mypy/typeshed/stdlib/posixpath.pyi b/mypy/typeshed/stdlib/posixpath.pyi index 31406f8df950..3313667f1781 100644 --- a/mypy/typeshed/stdlib/posixpath.pyi +++ b/mypy/typeshed/stdlib/posixpath.pyi @@ -77,7 +77,11 @@ pathsep: LiteralString defpath: LiteralString devnull: LiteralString -def abspath(path: PathLike[AnyStr] | AnyStr) -> AnyStr: ... +# Overloads are necessary to work around python/mypy#17952 & python/mypy#11880 +@overload +def abspath(path: PathLike[AnyStr]) -> AnyStr: ... +@overload +def abspath(path: AnyStr) -> AnyStr: ... @overload def basename(p: PathLike[AnyStr]) -> AnyStr: ... @overload @@ -86,8 +90,14 @@ def basename(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... def dirname(p: PathLike[AnyStr]) -> AnyStr: ... @overload def dirname(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... -def expanduser(path: PathLike[AnyStr] | AnyStr) -> AnyStr: ... -def expandvars(path: PathLike[AnyStr] | AnyStr) -> AnyStr: ... +@overload +def expanduser(path: PathLike[AnyStr]) -> AnyStr: ... +@overload +def expanduser(path: AnyStr) -> AnyStr: ... +@overload +def expandvars(path: PathLike[AnyStr]) -> AnyStr: ... +@overload +def expandvars(path: AnyStr) -> AnyStr: ... @overload def normcase(s: PathLike[AnyStr]) -> AnyStr: ... @overload From 050d12f8d5b07a892fc4e24fabc485cfafce3ff0 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 19 Oct 2024 16:30:06 -0700 Subject: [PATCH 7/7] Bump version to 1.12.1 --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index 7a022f8d3ce3..b7f966633d20 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.12.1+dev" +__version__ = "1.12.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))