From 324e7acc4bef1342963a0bd26f9b3708f906d06a Mon Sep 17 00:00:00 2001 From: KotlinIsland Date: Tue, 10 Oct 2023 00:45:19 +1000 Subject: [PATCH] intersections become Never --- mypy/checker.py | 7 +- mypy/checkexpr.py | 2 +- mypy/typeanal.py | 1 + mypy/typeops.py | 75 +++++++++++++++++++- test-data/unit/check-based-intersection.test | 25 +++++++ 5 files changed, 107 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7a38ec2c0..515bce0e4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -7093,10 +7093,15 @@ def conditional_types_with_intersection( out = [] errors: list[tuple[str, str]] = [] for v in possible_expr_types: - if not isinstance(v, Instance): + if isinstance(v, Instance): + if v.type.is_final: + return UninhabitedType(), expr_type + else: if not isinstance(v, IntersectionType): return yes_type, no_type for t in possible_target_types: + if t.type.is_final: + return UninhabitedType(), expr_type for v_item in v.items: v_type = get_proper_type(v_item) if isinstance(v_type, Instance) and self.intersect_instances( diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 66bd0cdef..2231c99d4 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -679,7 +679,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> if isinstance(ret_type, UnionType): ret_type = make_simplified_union(ret_type.items) if isinstance(ret_type, IntersectionType): - ret_type = make_simplified_intersection(ret_type.items) + ret_type = make_simplified_intersection(ret_type.items, check=self.chk) if isinstance(ret_type, UninhabitedType) and not ret_type.ambiguous: self.chk.binder.unreachable() # Warn on calls to functions that always return None. The check diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 4e9d11d41..230da34e0 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -105,6 +105,7 @@ "typing.Tuple", "typing.Type", "typing.Union", + "basedtyping.Intersection", *LITERAL_TYPE_NAMES, *ANNOTATED_TYPE_NAMES, } diff --git a/mypy/typeops.py b/mypy/typeops.py index 4b1e0e4a5..89a0095ca 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -11,9 +11,11 @@ from collections import defaultdict from typing import Any, Callable, Iterable, List, Sequence, TypeVar, cast +import mypy.checker from mypy.copytype import copy_type from mypy.expandtype import expand_type, expand_type_by_instance from mypy.maptype import map_instance_to_supertype +from mypy.mro import MroError, calculate_mro from mypy.nodes import ( ARG_POS, ARG_STAR, @@ -568,7 +570,12 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[ def make_simplified_intersection( - items: Sequence[Type], line: int = -1, column: int = -1, *, keep_erased: bool = False + items: Sequence[Type], + line: int = -1, + column: int = -1, + *, + keep_erased: bool = False, + check: mypy.checker.TypeChecker | None = None, ) -> ProperType: """Build intersection type with redundant intersection items removed. @@ -582,6 +589,7 @@ def make_simplified_intersection( * [int, Any] -> Intersection[int, Any] (Any types are not simplified away!) * [Any, Any] -> Any * [int, Intersection[bytes, str]] -> Intersection[int, bytes, str] + * [int, str] -> Never (invalid types become Never!) Note: This must NOT be used during semantic analysis, since TypeInfos may not be fully initialized. @@ -599,6 +607,24 @@ def make_simplified_intersection( # Step 3: remove redundant intersections simplified_set: Sequence[Type] = _remove_redundant_intersection_items(items, keep_erased) + if len(items) == 1: + return get_proper_type(items[0]) + + for item in items: + if isinstance(item, Instance): + if item.type.is_final: + return UninhabitedType() + if isinstance(item, LiteralType): + return UninhabitedType() + + if check: + for i in range(len(simplified_set)): + for j in range(len(simplified_set))[i:]: + l = simplified_set[i] + r = simplified_set[j] + if isinstance(l, Instance) and isinstance(r, Instance): + if _check_invalid_intersection((l, r), check): + return UninhabitedType() result = get_proper_type(IntersectionType.make_intersection(simplified_set, line, column)) # Step 5: At last, we erase any (inconsistent) extra attributes on instances. @@ -645,6 +671,53 @@ def _remove_redundant_intersection_items(items: list[Type], keep_erased: bool) - return [items[i] for i in range(len(items)) if i not in removed] +def _check_invalid_intersection( + instances: (Instance, Instance), check: mypy.checker.TypeChecker +) -> bool: + def _get_base_classes(instances_: (Instance, Instance)) -> list[Instance]: + base_classes_ = [] + for inst in instances_: + if inst.type.is_intersection: + expanded = inst.type.bases + else: + expanded = [inst] + + for expanded_inst in expanded: + base_classes_.append(expanded_inst) + return base_classes_ + + def make_fake_typeinfo(bases: list[Instance]) -> TypeInfo: + from mypy.nodes import Block, ClassDef, SymbolTable + + cdef = ClassDef("sus", Block([])) + info = TypeInfo(SymbolTable(), cdef, "amongus") + info.bases = bases + calculate_mro(info) + info.metaclass_type = info.calculate_metaclass_type() + return info + + base_classes = _get_base_classes(instances) + + bases = base_classes + try: + info = make_fake_typeinfo(bases) + with check.msg.filter_errors() as local_errors: + check.check_multiple_inheritance(info) + if local_errors.has_new_errors(): + # "class A(B, C)" unsafe, now check "class A(C, B)": + base_classes = _get_base_classes(instances[::-1]) + info = make_fake_typeinfo(base_classes) + with check.msg.filter_errors() as local_errors: + check.check_multiple_inheritance(info) + info.is_intersection = True + except MroError: + return True + if local_errors.has_new_errors(): + return True + + return False + + def _get_type_special_method_bool_ret_type(t: Type) -> Type | None: t = get_proper_type(t) diff --git a/test-data/unit/check-based-intersection.test b/test-data/unit/check-based-intersection.test index d6da5c4cc..cd15b5322 100644 --- a/test-data/unit/check-based-intersection.test +++ b/test-data/unit/check-based-intersection.test @@ -335,3 +335,28 @@ reveal_type(x) # N: Revealed type is "__main__.A & __main__.B" assert isinstance(x, C) reveal_type(x) # N: Revealed type is "__main__.A & __main__.B & __main__.C | __main__.A & __main__.C" [builtins fixtures/tuple.pyi] + + +[case testIntersectionAlias] +from basedtyping import Intersection +class A: ... +class B: ... +C = Intersection[A, B] +c: C + + +[case testTypeVarIntersectionMessage] +from typing import TypeVar + +class A: ... +class B: ... + +T = TypeVar("T", bound=A & B) # E: Use quoted types or "basedtyping.Intersection" + + +[case testIncompatibleIntersectionBecomesNever] +from __future__ import annotations +a: int & str | None +b: None = a +def f(a: int & str | None): + return a