Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

intersections become never #530

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"typing.Tuple",
"typing.Type",
"typing.Union",
"basedtyping.Intersection",
*LITERAL_TYPE_NAMES,
*ANNOTATED_TYPE_NAMES,
}
Expand Down
75 changes: 74 additions & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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)

Expand Down
25 changes: 25 additions & 0 deletions test-data/unit/check-based-intersection.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading