Skip to content

Commit 04ff70c

Browse files
authored
Fixes crash on partial None (#11134)
Closes #11105
1 parent 836ff14 commit 04ff70c

File tree

3 files changed

+46
-3
lines changed

3 files changed

+46
-3
lines changed

mypy/checker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3859,9 +3859,10 @@ def intersect_instances(self,
38593859
def _get_base_classes(instances_: Tuple[Instance, Instance]) -> List[Instance]:
38603860
base_classes_ = []
38613861
for inst in instances_:
3862-
expanded = [inst]
38633862
if inst.type.is_intersection:
38643863
expanded = inst.type.bases
3864+
else:
3865+
expanded = [inst]
38653866

38663867
for expanded_inst in expanded:
38673868
base_classes_.append(expanded_inst)

mypy/subtypes.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,13 @@ def visit_union_type(self, left: UnionType) -> bool:
477477

478478
def visit_partial_type(self, left: PartialType) -> bool:
479479
# This is indeterminate as we don't really know the complete type yet.
480-
raise RuntimeError
480+
if left.type is None:
481+
# Special case, partial `None`. This might happen when defining
482+
# class-level attributes with explicit `None`.
483+
# We can still recover from this.
484+
# https://github.com/python/mypy/issues/11105
485+
return self.visit_none_type(NoneType())
486+
raise RuntimeError(f'Partial type "{left}" cannot be checked with "issubtype()"')
481487

482488
def visit_type_type(self, left: TypeType) -> bool:
483489
right = self.right

test-data/unit/check-protocols.test

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2124,7 +2124,7 @@ class B(Protocol):
21242124
def execute(self, stmt: Any, *args: Any, **kwargs: Any) -> None: ...
21252125
def cool(self) -> None: ...
21262126

2127-
def func1(arg: A) -> None: ...
2127+
def func1(arg: A) -> None: ...
21282128
def func2(arg: Optional[A]) -> None: ...
21292129

21302130
x: B
@@ -2647,3 +2647,39 @@ class DataArray(ObjectHashable):
26472647
def f(self, x: Hashable) -> None:
26482648
reveal_type([self, x]) # N: Revealed type is "builtins.list[builtins.object*]"
26492649
[builtins fixtures/tuple.pyi]
2650+
2651+
2652+
[case testPartialAttributeNoneType]
2653+
# flags: --no-strict-optional
2654+
from typing import Optional, Protocol, runtime_checkable
2655+
2656+
@runtime_checkable
2657+
class MyProtocol(Protocol):
2658+
def is_valid(self) -> bool: ...
2659+
text: Optional[str]
2660+
2661+
class MyClass:
2662+
text = None
2663+
def is_valid(self) -> bool:
2664+
reveal_type(self.text) # N: Revealed type is "None"
2665+
assert isinstance(self, MyProtocol)
2666+
[builtins fixtures/isinstance.pyi]
2667+
[typing fixtures/typing-full.pyi]
2668+
2669+
2670+
[case testPartialAttributeNoneTypeStrictOptional]
2671+
# flags: --strict-optional
2672+
from typing import Optional, Protocol, runtime_checkable
2673+
2674+
@runtime_checkable
2675+
class MyProtocol(Protocol):
2676+
def is_valid(self) -> bool: ...
2677+
text: Optional[str]
2678+
2679+
class MyClass:
2680+
text = None
2681+
def is_valid(self) -> bool:
2682+
reveal_type(self.text) # N: Revealed type is "None"
2683+
assert isinstance(self, MyProtocol)
2684+
[builtins fixtures/isinstance.pyi]
2685+
[typing fixtures/typing-full.pyi]

0 commit comments

Comments
 (0)