Skip to content

Commit b1c9e3f

Browse files
Merge branch 'master' into fix-3-arg-range
2 parents 3ad0bad + fb16e93 commit b1c9e3f

File tree

10 files changed

+158
-115
lines changed

10 files changed

+158
-115
lines changed

.github/workflows/test_stubgenc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
- 'mypy/stubgenc.py'
1212
- 'mypy/stubdoc.py'
1313
- 'mypy/stubutil.py'
14-
- 'test-data/stubgen/**'
14+
- 'test-data/pybind11_fixtures/**'
1515

1616
permissions:
1717
contents: read

mypy/checker.py

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7905,6 +7905,10 @@ def is_writable_attribute(self, node: Node) -> bool:
79057905
return False
79067906

79077907
def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
7908+
"""Get the type(s) resulting from an isinstance check.
7909+
7910+
Returns an empty list for isinstance(x, ()).
7911+
"""
79087912
if isinstance(expr, OpExpr) and expr.op == "|":
79097913
left = self.get_isinstance_type(expr.left)
79107914
if left is None and is_literal_none(expr.left):
@@ -7944,11 +7948,6 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
79447948
types.append(TypeRange(typ, is_upper_bound=False))
79457949
else: # we didn't see an actual type, but rather a variable with unknown value
79467950
return None
7947-
if not types:
7948-
# this can happen if someone has empty tuple as 2nd argument to isinstance
7949-
# strictly speaking, we should return UninhabitedType but for simplicity we will simply
7950-
# refuse to do any type inference for now
7951-
return None
79527951
return types
79537952

79547953
def is_literal_enum(self, n: Expression) -> bool:
@@ -8185,59 +8184,82 @@ def conditional_types(
81858184
UninhabitedType means unreachable.
81868185
None means no new information can be inferred.
81878186
"""
8188-
if proposed_type_ranges:
8189-
if len(proposed_type_ranges) == 1:
8190-
target = proposed_type_ranges[0].item
8191-
target = get_proper_type(target)
8192-
if isinstance(target, LiteralType) and (
8193-
target.is_enum_literal() or isinstance(target.value, bool)
8194-
):
8195-
enum_name = target.fallback.type.fullname
8196-
current_type = try_expanding_sum_type_to_union(current_type, enum_name)
8197-
proposed_items = [type_range.item for type_range in proposed_type_ranges]
8198-
proposed_type = make_simplified_union(proposed_items)
8199-
if isinstance(get_proper_type(current_type), AnyType):
8200-
return proposed_type, current_type
8201-
elif isinstance(proposed_type, AnyType):
8202-
# We don't really know much about the proposed type, so we shouldn't
8203-
# attempt to narrow anything. Instead, we broaden the expr to Any to
8204-
# avoid false positives
8205-
return proposed_type, default
8206-
elif not any(type_range.is_upper_bound for type_range in proposed_type_ranges) and (
8207-
# concrete subtypes
8208-
is_proper_subtype(current_type, proposed_type, ignore_promotions=True)
8209-
# structural subtypes
8210-
or (
8211-
(
8212-
isinstance(proposed_type, CallableType)
8213-
or (isinstance(proposed_type, Instance) and proposed_type.type.is_protocol)
8214-
)
8215-
and is_subtype(current_type, proposed_type, ignore_promotions=True)
8216-
)
8187+
if proposed_type_ranges is None:
8188+
# An isinstance check, but we don't understand the type
8189+
return current_type, default
8190+
8191+
if not proposed_type_ranges:
8192+
# This is the case for `if isinstance(x, ())` which always returns False.
8193+
return UninhabitedType(), default
8194+
8195+
if len(proposed_type_ranges) == 1:
8196+
# expand e.g. bool -> Literal[True] | Literal[False]
8197+
target = proposed_type_ranges[0].item
8198+
target = get_proper_type(target)
8199+
if isinstance(target, LiteralType) and (
8200+
target.is_enum_literal() or isinstance(target.value, bool)
82178201
):
8218-
# Expression is always of one of the types in proposed_type_ranges
8219-
return default, UninhabitedType()
8220-
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
8221-
# Expression is never of any type in proposed_type_ranges
8222-
return UninhabitedType(), default
8223-
else:
8224-
# we can only restrict when the type is precise, not bounded
8225-
proposed_precise_type = UnionType.make_union(
8226-
[
8227-
type_range.item
8228-
for type_range in proposed_type_ranges
8229-
if not type_range.is_upper_bound
8230-
]
8231-
)
8232-
remaining_type = restrict_subtype_away(
8233-
current_type,
8234-
proposed_precise_type,
8202+
enum_name = target.fallback.type.fullname
8203+
current_type = try_expanding_sum_type_to_union(current_type, enum_name)
8204+
8205+
proper_type = get_proper_type(current_type)
8206+
# factorize over union types: isinstance(A|B, C) -> yes = A_yes | B_yes
8207+
if isinstance(proper_type, UnionType):
8208+
result: list[tuple[Type | None, Type | None]] = [
8209+
conditional_types(
8210+
union_item,
8211+
proposed_type_ranges,
8212+
default=union_item,
82358213
consider_runtime_isinstance=consider_runtime_isinstance,
82368214
)
8237-
return proposed_type, remaining_type
8215+
for union_item in get_proper_types(proper_type.items)
8216+
]
8217+
# separate list of tuples into two lists
8218+
yes_types, no_types = zip(*result)
8219+
proposed_type = make_simplified_union([t for t in yes_types if t is not None])
82388220
else:
8239-
# An isinstance check, but we don't understand the type
8240-
return current_type, default
8221+
proposed_items = [type_range.item for type_range in proposed_type_ranges]
8222+
proposed_type = make_simplified_union(proposed_items)
8223+
8224+
if isinstance(proper_type, AnyType):
8225+
return proposed_type, current_type
8226+
elif isinstance(proposed_type, AnyType):
8227+
# We don't really know much about the proposed type, so we shouldn't
8228+
# attempt to narrow anything. Instead, we broaden the expr to Any to
8229+
# avoid false positives
8230+
return proposed_type, default
8231+
elif not any(type_range.is_upper_bound for type_range in proposed_type_ranges) and (
8232+
# concrete subtypes
8233+
is_proper_subtype(current_type, proposed_type, ignore_promotions=True)
8234+
# structural subtypes
8235+
or (
8236+
(
8237+
isinstance(proposed_type, CallableType)
8238+
or (isinstance(proposed_type, Instance) and proposed_type.type.is_protocol)
8239+
)
8240+
and is_subtype(current_type, proposed_type, ignore_promotions=True)
8241+
)
8242+
):
8243+
# Expression is always of one of the types in proposed_type_ranges
8244+
return default, UninhabitedType()
8245+
elif not is_overlapping_types(current_type, proposed_type, ignore_promotions=True):
8246+
# Expression is never of any type in proposed_type_ranges
8247+
return UninhabitedType(), default
8248+
else:
8249+
# we can only restrict when the type is precise, not bounded
8250+
proposed_precise_type = UnionType.make_union(
8251+
[
8252+
type_range.item
8253+
for type_range in proposed_type_ranges
8254+
if not type_range.is_upper_bound
8255+
]
8256+
)
8257+
remaining_type = restrict_subtype_away(
8258+
current_type,
8259+
proposed_precise_type,
8260+
consider_runtime_isinstance=consider_runtime_isinstance,
8261+
)
8262+
return proposed_type, remaining_type
82418263

82428264

82438265
def conditional_types_to_typemaps(
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import os
1+
import pathlib
2+
import typing
23
from . import demo as demo
34
from typing import overload
45

56
class StaticMethods:
67
def __init__(self, *args, **kwargs) -> None: ...
78
@overload
89
@staticmethod
9-
def overloaded_static_method(value: int) -> int: ...
10+
def overloaded_static_method(value: typing.SupportsInt) -> int: ...
1011
@overload
1112
@staticmethod
12-
def overloaded_static_method(value: float) -> float: ...
13+
def overloaded_static_method(value: typing.SupportsFloat) -> float: ...
1314
@staticmethod
14-
def some_static_method(a: int, b: int) -> int: ...
15+
def some_static_method(a: typing.SupportsInt, b: typing.SupportsInt) -> int: ...
1516

1617
class TestStruct:
1718
field_readwrite: int
@@ -23,5 +24,5 @@ class TestStruct:
2324
def func_incomplete_signature(*args, **kwargs): ...
2425
def func_returning_optional() -> int | None: ...
2526
def func_returning_pair() -> tuple[int, float]: ...
26-
def func_returning_path() -> os.PathLike: ...
27+
def func_returning_path() -> pathlib.Path: ...
2728
def func_returning_vector() -> list[float]: ...

test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import typing
12
from typing import ClassVar, overload
23

34
PI: float
@@ -9,7 +10,7 @@ class Point:
910
__entries: ClassVar[dict] = ...
1011
degree: ClassVar[Point.AngleUnit] = ...
1112
radian: ClassVar[Point.AngleUnit] = ...
12-
def __init__(self, value: int) -> None: ...
13+
def __init__(self, value: typing.SupportsInt) -> None: ...
1314
def __eq__(self, other: object) -> bool: ...
1415
def __hash__(self) -> int: ...
1516
def __index__(self) -> int: ...
@@ -26,7 +27,7 @@ class Point:
2627
inch: ClassVar[Point.LengthUnit] = ...
2728
mm: ClassVar[Point.LengthUnit] = ...
2829
pixel: ClassVar[Point.LengthUnit] = ...
29-
def __init__(self, value: int) -> None: ...
30+
def __init__(self, value: typing.SupportsInt) -> None: ...
3031
def __eq__(self, other: object) -> bool: ...
3132
def __hash__(self) -> int: ...
3233
def __index__(self) -> int: ...
@@ -46,16 +47,16 @@ class Point:
4647
@overload
4748
def __init__(self) -> None: ...
4849
@overload
49-
def __init__(self, x: float, y: float) -> None: ...
50+
def __init__(self, x: typing.SupportsFloat, y: typing.SupportsFloat) -> None: ...
5051
def as_list(self) -> list[float]: ...
5152
@overload
52-
def distance_to(self, x: float, y: float) -> float: ...
53+
def distance_to(self, x: typing.SupportsFloat, y: typing.SupportsFloat) -> float: ...
5354
@overload
5455
def distance_to(self, other: Point) -> float: ...
5556
@property
5657
def length(self) -> float: ...
5758

5859
def answer() -> int: ...
59-
def midpoint(left: float, right: float) -> float: ...
60-
def sum(arg0: int, arg1: int) -> int: ...
61-
def weighted_midpoint(left: float, right: float, alpha: float = ...) -> float: ...
60+
def midpoint(left: typing.SupportsFloat, right: typing.SupportsFloat) -> float: ...
61+
def sum(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> int: ...
62+
def weighted_midpoint(left: typing.SupportsFloat, right: typing.SupportsFloat, alpha: typing.SupportsFloat = ...) -> float: ...
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import os
1+
import pathlib
2+
import typing
23
from . import demo as demo
34
from typing import overload
45

@@ -7,27 +8,27 @@ class StaticMethods:
78
"""Initialize self. See help(type(self)) for accurate signature."""
89
@overload
910
@staticmethod
10-
def overloaded_static_method(value: int) -> int:
11+
def overloaded_static_method(value: typing.SupportsInt) -> int:
1112
"""overloaded_static_method(*args, **kwargs)
1213
Overloaded function.
1314
14-
1. overloaded_static_method(value: int) -> int
15+
1. overloaded_static_method(value: typing.SupportsInt) -> int
1516
16-
2. overloaded_static_method(value: float) -> float
17+
2. overloaded_static_method(value: typing.SupportsFloat) -> float
1718
"""
1819
@overload
1920
@staticmethod
20-
def overloaded_static_method(value: float) -> float:
21+
def overloaded_static_method(value: typing.SupportsFloat) -> float:
2122
"""overloaded_static_method(*args, **kwargs)
2223
Overloaded function.
2324
24-
1. overloaded_static_method(value: int) -> int
25+
1. overloaded_static_method(value: typing.SupportsInt) -> int
2526
26-
2. overloaded_static_method(value: float) -> float
27+
2. overloaded_static_method(value: typing.SupportsFloat) -> float
2728
"""
2829
@staticmethod
29-
def some_static_method(a: int, b: int) -> int:
30-
"""some_static_method(a: int, b: int) -> int
30+
def some_static_method(a: typing.SupportsInt, b: typing.SupportsInt) -> int:
31+
"""some_static_method(a: typing.SupportsInt, b: typing.SupportsInt) -> int
3132
3233
None
3334
"""
@@ -46,10 +47,10 @@ class TestStruct:
4647
def func_incomplete_signature(*args, **kwargs):
4748
"""func_incomplete_signature() -> dummy_sub_namespace::HasNoBinding"""
4849
def func_returning_optional() -> int | None:
49-
"""func_returning_optional() -> Optional[int]"""
50+
"""func_returning_optional() -> int | None"""
5051
def func_returning_pair() -> tuple[int, float]:
51-
"""func_returning_pair() -> Tuple[int, float]"""
52-
def func_returning_path() -> os.PathLike:
53-
"""func_returning_path() -> os.PathLike"""
52+
"""func_returning_pair() -> tuple[int, float]"""
53+
def func_returning_path() -> pathlib.Path:
54+
"""func_returning_path() -> pathlib.Path"""
5455
def func_returning_vector() -> list[float]:
55-
"""func_returning_vector() -> List[float]"""
56+
"""func_returning_vector() -> list[float]"""

0 commit comments

Comments
 (0)