Skip to content

Commit fa8121e

Browse files
Merge branch 'master' into meet_literal
2 parents bd498d8 + de6e742 commit fa8121e

File tree

4 files changed

+104
-26
lines changed

4 files changed

+104
-26
lines changed

mypy/checker.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6218,21 +6218,26 @@ def find_isinstance_check_helper(
62186218
attr = try_getting_str_literals(node.args[1], self.lookup_type(node.args[1]))
62196219
if literal(expr) == LITERAL_TYPE and attr and len(attr) == 1:
62206220
return self.hasattr_type_maps(expr, self.lookup_type(expr), attr[0])
6221-
elif isinstance(node.callee, RefExpr):
6222-
if node.callee.type_guard is not None or node.callee.type_is is not None:
6221+
else:
6222+
type_is, type_guard = None, None
6223+
called_type = self.lookup_type_or_none(node.callee)
6224+
if called_type is not None:
6225+
called_type = get_proper_type(called_type)
6226+
# TODO: there are some more cases in check_call() to handle.
6227+
# If the callee is an instance, try to extract TypeGuard/TypeIs from its __call__ method.
6228+
if isinstance(called_type, Instance):
6229+
call = find_member("__call__", called_type, called_type, is_operator=True)
6230+
if call is not None:
6231+
called_type = get_proper_type(call)
6232+
if isinstance(called_type, CallableType):
6233+
type_is, type_guard = called_type.type_is, called_type.type_guard
6234+
6235+
# If the callee is a RefExpr, extract TypeGuard/TypeIs directly.
6236+
if isinstance(node.callee, RefExpr):
6237+
type_is, type_guard = node.callee.type_is, node.callee.type_guard
6238+
if type_guard is not None or type_is is not None:
62236239
# TODO: Follow *args, **kwargs
62246240
if node.arg_kinds[0] != nodes.ARG_POS:
6225-
# the first argument might be used as a kwarg
6226-
called_type = get_proper_type(self.lookup_type(node.callee))
6227-
6228-
# TODO: there are some more cases in check_call() to handle.
6229-
if isinstance(called_type, Instance):
6230-
call = find_member(
6231-
"__call__", called_type, called_type, is_operator=True
6232-
)
6233-
if call is not None:
6234-
called_type = get_proper_type(call)
6235-
62366241
# *assuming* the overloaded function is correct, there's a couple cases:
62376242
# 1) The first argument has different names, but is pos-only. We don't
62386243
# care about this case, the argument must be passed positionally.
@@ -6245,9 +6250,7 @@ def find_isinstance_check_helper(
62456250
# we want the idx-th variable to be narrowed
62466251
expr = collapse_walrus(node.args[idx])
62476252
else:
6248-
kind = (
6249-
"guard" if node.callee.type_guard is not None else "narrower"
6250-
)
6253+
kind = "guard" if type_guard is not None else "narrower"
62516254
self.fail(
62526255
message_registry.TYPE_GUARD_POS_ARG_REQUIRED.format(kind), node
62536256
)
@@ -6258,15 +6261,15 @@ def find_isinstance_check_helper(
62586261
# considered "always right" (i.e. even if the types are not overlapping).
62596262
# Also note that a care must be taken to unwrap this back at read places
62606263
# where we use this to narrow down declared type.
6261-
if node.callee.type_guard is not None:
6262-
return {expr: TypeGuardedType(node.callee.type_guard)}, {}
6264+
if type_guard is not None:
6265+
return {expr: TypeGuardedType(type_guard)}, {}
62636266
else:
6264-
assert node.callee.type_is is not None
6267+
assert type_is is not None
62656268
return conditional_types_to_typemaps(
62666269
expr,
62676270
*self.conditional_types_with_intersection(
62686271
self.lookup_type(expr),
6269-
[TypeRange(node.callee.type_is, is_upper_bound=False)],
6272+
[TypeRange(type_is, is_upper_bound=False)],
62706273
expr,
62716274
consider_runtime_isinstance=False,
62726275
),

mypy/config_parser.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717

1818
from collections.abc import Mapping, MutableMapping, Sequence
1919
from typing import Any, Callable, Final, TextIO, Union
20-
from typing_extensions import TypeAlias as _TypeAlias
20+
from typing_extensions import Never, TypeAlias
2121

2222
from mypy import defaults
2323
from mypy.options import PER_MODULE_OPTIONS, Options
2424

25-
_CONFIG_VALUE_TYPES: _TypeAlias = Union[
25+
_CONFIG_VALUE_TYPES: TypeAlias = Union[
2626
str, bool, int, float, dict[str, str], list[str], tuple[int, int]
2727
]
28-
_INI_PARSER_CALLABLE: _TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES]
28+
_INI_PARSER_CALLABLE: TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES]
2929

3030

3131
class VersionTypeError(argparse.ArgumentTypeError):
@@ -60,14 +60,31 @@ def parse_version(v: str | float) -> tuple[int, int]:
6060
return major, minor
6161

6262

63-
def try_split(v: str | Sequence[str], split_regex: str = "[,]") -> list[str]:
64-
"""Split and trim a str or list of str into a list of str"""
63+
def try_split(v: str | Sequence[str] | object, split_regex: str = ",") -> list[str]:
64+
"""Split and trim a str or sequence (eg: list) of str into a list of str.
65+
If an element of the input is not str, a type error will be raised."""
66+
67+
def complain(x: object, additional_info: str = "") -> Never:
68+
raise argparse.ArgumentTypeError(
69+
f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x).__name__}.{additional_info}"
70+
)
71+
6572
if isinstance(v, str):
6673
items = [p.strip() for p in re.split(split_regex, v)]
6774
if items and items[-1] == "":
6875
items.pop(-1)
6976
return items
70-
return [p.strip() for p in v]
77+
elif isinstance(v, Sequence):
78+
return [
79+
(
80+
p.strip()
81+
if isinstance(p, str)
82+
else complain(p, additional_info=" (As an element of the list.)")
83+
)
84+
for p in v
85+
]
86+
else:
87+
complain(v)
7188

7289

7390
def validate_codes(codes: list[str]) -> list[str]:

test-data/unit/check-typeguard.test

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,53 @@ assert a(x=x)
731731
reveal_type(x) # N: Revealed type is "builtins.int"
732732
[builtins fixtures/tuple.pyi]
733733

734+
# https://github.com/python/mypy/issues/19575
735+
[case testNoCrashOnDunderCallTypeGuardTemporaryObject]
736+
from typing_extensions import TypeGuard
737+
class E:
738+
def __init__(self) -> None: ...
739+
def __call__(self, o: object) -> TypeGuard[int]:
740+
return True
741+
x = object()
742+
if E()(x):
743+
reveal_type(x) # N: Revealed type is "builtins.int"
744+
[builtins fixtures/tuple.pyi]
745+
746+
[case testNoCrashOnDunderCallTypeIsTemporaryObject]
747+
from typing_extensions import TypeIs
748+
class E:
749+
def __init__(self) -> None: ...
750+
def __call__(self, o: object) -> TypeIs[int]:
751+
return True
752+
x = object()
753+
if E()(x):
754+
reveal_type(x) # N: Revealed type is "builtins.int"
755+
[builtins fixtures/tuple.pyi]
756+
757+
[case testNoCrashOnDunderCallTypeIsTemporaryObjectGeneric]
758+
from typing import Generic, TypeVar
759+
from typing_extensions import TypeIs
760+
T = TypeVar("T")
761+
class E(Generic[T]):
762+
def __init__(self) -> None: ...
763+
def __call__(self, o: object) -> TypeIs[T]:
764+
return True
765+
x = object()
766+
if E[int]()(x):
767+
reveal_type(x) # N: Revealed type is "builtins.int"
768+
[builtins fixtures/tuple.pyi]
769+
770+
[case testTypeGuardTemporaryObjectWithKeywordArg]
771+
from typing_extensions import TypeGuard
772+
class E:
773+
def __init__(self) -> None: ...
774+
def __call__(self, o: object) -> TypeGuard[int]:
775+
return True
776+
x = object()
777+
if E()(o=x):
778+
reveal_type(x) # N: Revealed type is "builtins.int"
779+
[builtins fixtures/tuple.pyi]
780+
734781
[case testTypeGuardRestrictAwaySingleInvariant]
735782
from typing import List
736783
from typing_extensions import TypeGuard

test-data/unit/cmdline.pyproject.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,14 @@ y: int = 'y' # E: Incompatible types in assignment (expression has type "str",
226226
# This should not trigger any errors, because it is not included:
227227
z: int = 'z'
228228
[out]
229+
230+
[case testPyprojectTOMLSettingOfWrongType]
231+
# cmd: mypy a.py
232+
[file pyproject.toml]
233+
\[tool.mypy]
234+
enable_error_code = true
235+
[file a.py]
236+
x: int = 1
237+
[out]
238+
pyproject.toml: [mypy]: enable_error_code: Expected a list or a stringified version thereof, but got: 'True', of type bool.
239+
== Return code: 0

0 commit comments

Comments
 (0)