Skip to content

Commit 324e7ac

Browse files
committed
intersections become Never
1 parent eba432a commit 324e7ac

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

mypy/checker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7093,10 +7093,15 @@ def conditional_types_with_intersection(
70937093
out = []
70947094
errors: list[tuple[str, str]] = []
70957095
for v in possible_expr_types:
7096-
if not isinstance(v, Instance):
7096+
if isinstance(v, Instance):
7097+
if v.type.is_final:
7098+
return UninhabitedType(), expr_type
7099+
else:
70977100
if not isinstance(v, IntersectionType):
70987101
return yes_type, no_type
70997102
for t in possible_target_types:
7103+
if t.type.is_final:
7104+
return UninhabitedType(), expr_type
71007105
for v_item in v.items:
71017106
v_type = get_proper_type(v_item)
71027107
if isinstance(v_type, Instance) and self.intersect_instances(

mypy/checkexpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
679679
if isinstance(ret_type, UnionType):
680680
ret_type = make_simplified_union(ret_type.items)
681681
if isinstance(ret_type, IntersectionType):
682-
ret_type = make_simplified_intersection(ret_type.items)
682+
ret_type = make_simplified_intersection(ret_type.items, check=self.chk)
683683
if isinstance(ret_type, UninhabitedType) and not ret_type.ambiguous:
684684
self.chk.binder.unreachable()
685685
# Warn on calls to functions that always return None. The check

mypy/typeanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"typing.Tuple",
106106
"typing.Type",
107107
"typing.Union",
108+
"basedtyping.Intersection",
108109
*LITERAL_TYPE_NAMES,
109110
*ANNOTATED_TYPE_NAMES,
110111
}

mypy/typeops.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from collections import defaultdict
1212
from typing import Any, Callable, Iterable, List, Sequence, TypeVar, cast
1313

14+
import mypy.checker
1415
from mypy.copytype import copy_type
1516
from mypy.expandtype import expand_type, expand_type_by_instance
1617
from mypy.maptype import map_instance_to_supertype
18+
from mypy.mro import MroError, calculate_mro
1719
from mypy.nodes import (
1820
ARG_POS,
1921
ARG_STAR,
@@ -568,7 +570,12 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[
568570

569571

570572
def make_simplified_intersection(
571-
items: Sequence[Type], line: int = -1, column: int = -1, *, keep_erased: bool = False
573+
items: Sequence[Type],
574+
line: int = -1,
575+
column: int = -1,
576+
*,
577+
keep_erased: bool = False,
578+
check: mypy.checker.TypeChecker | None = None,
572579
) -> ProperType:
573580
"""Build intersection type with redundant intersection items removed.
574581
@@ -582,6 +589,7 @@ def make_simplified_intersection(
582589
* [int, Any] -> Intersection[int, Any] (Any types are not simplified away!)
583590
* [Any, Any] -> Any
584591
* [int, Intersection[bytes, str]] -> Intersection[int, bytes, str]
592+
* [int, str] -> Never (invalid types become Never!)
585593
586594
Note: This must NOT be used during semantic analysis, since TypeInfos may not
587595
be fully initialized.
@@ -599,6 +607,24 @@ def make_simplified_intersection(
599607
# Step 3: remove redundant intersections
600608
simplified_set: Sequence[Type] = _remove_redundant_intersection_items(items, keep_erased)
601609

610+
if len(items) == 1:
611+
return get_proper_type(items[0])
612+
613+
for item in items:
614+
if isinstance(item, Instance):
615+
if item.type.is_final:
616+
return UninhabitedType()
617+
if isinstance(item, LiteralType):
618+
return UninhabitedType()
619+
620+
if check:
621+
for i in range(len(simplified_set)):
622+
for j in range(len(simplified_set))[i:]:
623+
l = simplified_set[i]
624+
r = simplified_set[j]
625+
if isinstance(l, Instance) and isinstance(r, Instance):
626+
if _check_invalid_intersection((l, r), check):
627+
return UninhabitedType()
602628
result = get_proper_type(IntersectionType.make_intersection(simplified_set, line, column))
603629

604630
# 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) -
645671
return [items[i] for i in range(len(items)) if i not in removed]
646672

647673

674+
def _check_invalid_intersection(
675+
instances: (Instance, Instance), check: mypy.checker.TypeChecker
676+
) -> bool:
677+
def _get_base_classes(instances_: (Instance, Instance)) -> list[Instance]:
678+
base_classes_ = []
679+
for inst in instances_:
680+
if inst.type.is_intersection:
681+
expanded = inst.type.bases
682+
else:
683+
expanded = [inst]
684+
685+
for expanded_inst in expanded:
686+
base_classes_.append(expanded_inst)
687+
return base_classes_
688+
689+
def make_fake_typeinfo(bases: list[Instance]) -> TypeInfo:
690+
from mypy.nodes import Block, ClassDef, SymbolTable
691+
692+
cdef = ClassDef("sus", Block([]))
693+
info = TypeInfo(SymbolTable(), cdef, "amongus")
694+
info.bases = bases
695+
calculate_mro(info)
696+
info.metaclass_type = info.calculate_metaclass_type()
697+
return info
698+
699+
base_classes = _get_base_classes(instances)
700+
701+
bases = base_classes
702+
try:
703+
info = make_fake_typeinfo(bases)
704+
with check.msg.filter_errors() as local_errors:
705+
check.check_multiple_inheritance(info)
706+
if local_errors.has_new_errors():
707+
# "class A(B, C)" unsafe, now check "class A(C, B)":
708+
base_classes = _get_base_classes(instances[::-1])
709+
info = make_fake_typeinfo(base_classes)
710+
with check.msg.filter_errors() as local_errors:
711+
check.check_multiple_inheritance(info)
712+
info.is_intersection = True
713+
except MroError:
714+
return True
715+
if local_errors.has_new_errors():
716+
return True
717+
718+
return False
719+
720+
648721
def _get_type_special_method_bool_ret_type(t: Type) -> Type | None:
649722
t = get_proper_type(t)
650723

test-data/unit/check-based-intersection.test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,28 @@ reveal_type(x) # N: Revealed type is "__main__.A & __main__.B"
335335
assert isinstance(x, C)
336336
reveal_type(x) # N: Revealed type is "__main__.A & __main__.B & __main__.C | __main__.A & __main__.C"
337337
[builtins fixtures/tuple.pyi]
338+
339+
340+
[case testIntersectionAlias]
341+
from basedtyping import Intersection
342+
class A: ...
343+
class B: ...
344+
C = Intersection[A, B]
345+
c: C
346+
347+
348+
[case testTypeVarIntersectionMessage]
349+
from typing import TypeVar
350+
351+
class A: ...
352+
class B: ...
353+
354+
T = TypeVar("T", bound=A & B) # E: Use quoted types or "basedtyping.Intersection"
355+
356+
357+
[case testIncompatibleIntersectionBecomesNever]
358+
from __future__ import annotations
359+
a: int & str | None
360+
b: None = a
361+
def f(a: int & str | None):
362+
return a

0 commit comments

Comments
 (0)