Infer ParamSpec constraint from arguments (#15896)
Fixes https://github.com/python/mypy/issues/12278
Fixes https://github.com/python/mypy/issues/13191 (more tricky nested
use cases with optional/keyword args still don't work, but they are
quite tricky to fix and may selectively fixed later)
This unfortunately requires some special-casing, here is its summary:
* If actual argument for `Callable[P, T]` is non-generic and non-lambda,
do not put it into inference second pass.
* If we are able to infer constraints for `P` without using arguments
mapped to `*args: P.args` etc., do not add the constraint for `P` vs
those arguments (this applies to both top-level callable constraints,
and for nested callable constraints against callables that are known to
have imprecise argument kinds).
(Btw TODO I added is not related to this PR, I just noticed something
obviously wrong)
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 6de317f..4430d07 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -1987,7 +1987,7 @@
)
arg_pass_nums = self.get_arg_infer_passes(
- callee_type.arg_types, formal_to_actual, len(args)
+ callee_type, args, arg_types, formal_to_actual, len(args)
)
pass1_args: list[Type | None] = []
@@ -2001,6 +2001,7 @@
callee_type,
pass1_args,
arg_kinds,
+ arg_names,
formal_to_actual,
context=self.argument_infer_context(),
strict=self.chk.in_checked_function(),
@@ -2061,6 +2062,7 @@
callee_type,
arg_types,
arg_kinds,
+ arg_names,
formal_to_actual,
context=self.argument_infer_context(),
strict=self.chk.in_checked_function(),
@@ -2140,6 +2142,7 @@
callee_type,
arg_types,
arg_kinds,
+ arg_names,
formal_to_actual,
context=self.argument_infer_context(),
)
@@ -2152,7 +2155,12 @@
)
def get_arg_infer_passes(
- self, arg_types: list[Type], formal_to_actual: list[list[int]], num_actuals: int
+ self,
+ callee: CallableType,
+ args: list[Expression],
+ arg_types: list[Type],
+ formal_to_actual: list[list[int]],
+ num_actuals: int,
) -> list[int]:
"""Return pass numbers for args for two-pass argument type inference.
@@ -2163,8 +2171,28 @@
lambdas more effectively.
"""
res = [1] * num_actuals
- for i, arg in enumerate(arg_types):
- if arg.accept(ArgInferSecondPassQuery()):
+ for i, arg in enumerate(callee.arg_types):
+ skip_param_spec = False
+ p_formal = get_proper_type(callee.arg_types[i])
+ if isinstance(p_formal, CallableType) and p_formal.param_spec():
+ for j in formal_to_actual[i]:
+ p_actual = get_proper_type(arg_types[j])
+ # This is an exception from the usual logic where we put generic Callable
+ # arguments in the second pass. If we have a non-generic actual, it is
+ # likely to infer good constraints, for example if we have:
+ # def run(Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ...
+ # def test(x: int, y: int) -> int: ...
+ # run(test, 1, 2)
+ # we will use `test` for inference, since it will allow to infer also
+ # argument *names* for P <: [x: int, y: int].
+ if (
+ isinstance(p_actual, CallableType)
+ and not p_actual.variables
+ and not isinstance(args[j], LambdaExpr)
+ ):
+ skip_param_spec = True
+ break
+ if not skip_param_spec and arg.accept(ArgInferSecondPassQuery()):
for j in formal_to_actual[i]:
res[j] = 2
return res
@@ -4903,7 +4931,9 @@
self.chk.fail(message_registry.CANNOT_INFER_LAMBDA_TYPE, e)
return None, None
- return callable_ctx, callable_ctx
+ # Type of lambda must have correct argument names, to prevent false
+ # negatives when lambdas appear in `ParamSpec` context.
+ return callable_ctx.copy_modified(arg_names=e.arg_names), callable_ctx
def visit_super_expr(self, e: SuperExpr) -> Type:
"""Type check a super expression (non-lvalue)."""
@@ -5921,6 +5951,7 @@
super().__init__(types.ANY_STRATEGY)
def visit_callable_type(self, t: CallableType) -> bool:
+ # TODO: we need to check only for type variables of original callable.
return self.query_types(t.arg_types) or t.accept(HasTypeVarQuery())
diff --git a/mypy/constraints.py b/mypy/constraints.py
index edce11e..0e59b54 100644
--- a/mypy/constraints.py
+++ b/mypy/constraints.py
@@ -108,6 +108,7 @@
callee: CallableType,
arg_types: Sequence[Type | None],
arg_kinds: list[ArgKind],
+ arg_names: Sequence[str | None] | None,
formal_to_actual: list[list[int]],
context: ArgumentInferContext,
) -> list[Constraint]:
@@ -118,6 +119,20 @@
constraints: list[Constraint] = []
mapper = ArgTypeExpander(context)
+ param_spec = callee.param_spec()
+ param_spec_arg_types = []
+ param_spec_arg_names = []
+ param_spec_arg_kinds = []
+
+ incomplete_star_mapping = False
+ for i, actuals in enumerate(formal_to_actual):
+ for actual in actuals:
+ if actual is None and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2):
+ # We can't use arguments to infer ParamSpec constraint, if only some
+ # are present in the current inference pass.
+ incomplete_star_mapping = True
+ break
+
for i, actuals in enumerate(formal_to_actual):
if isinstance(callee.arg_types[i], UnpackType):
unpack_type = callee.arg_types[i]
@@ -194,11 +209,47 @@
actual_type = mapper.expand_actual_type(
actual_arg_type, arg_kinds[actual], callee.arg_names[i], callee.arg_kinds[i]
)
- # TODO: if callee has ParamSpec, we need to collect all actuals that map to star
- # args and create single constraint between P and resulting Parameters instead.
- c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF)
- constraints.extend(c)
-
+ if (
+ param_spec
+ and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2)
+ and not incomplete_star_mapping
+ ):
+ # If actual arguments are mapped to ParamSpec type, we can't infer individual
+ # constraints, instead store them and infer single constraint at the end.
+ # It is impossible to map actual kind to formal kind, so use some heuristic.
+ # This inference is used as a fallback, so relying on heuristic should be OK.
+ param_spec_arg_types.append(
+ mapper.expand_actual_type(
+ actual_arg_type, arg_kinds[actual], None, arg_kinds[actual]
+ )
+ )
+ actual_kind = arg_kinds[actual]
+ param_spec_arg_kinds.append(
+ ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind
+ )
+ param_spec_arg_names.append(arg_names[actual] if arg_names else None)
+ else:
+ c = infer_constraints(callee.arg_types[i], actual_type, SUPERTYPE_OF)
+ constraints.extend(c)
+ if (
+ param_spec
+ and not any(c.type_var == param_spec.id for c in constraints)
+ and not incomplete_star_mapping
+ ):
+ # Use ParamSpec constraint from arguments only if there are no other constraints,
+ # since as explained above it is quite ad-hoc.
+ constraints.append(
+ Constraint(
+ param_spec,
+ SUPERTYPE_OF,
+ Parameters(
+ arg_types=param_spec_arg_types,
+ arg_kinds=param_spec_arg_kinds,
+ arg_names=param_spec_arg_names,
+ imprecise_arg_kinds=True,
+ ),
+ )
+ )
return constraints
@@ -949,6 +1000,14 @@
res: list[Constraint] = []
cactual = self.actual.with_unpacked_kwargs()
param_spec = template.param_spec()
+
+ template_ret_type, cactual_ret_type = template.ret_type, cactual.ret_type
+ if template.type_guard is not None:
+ template_ret_type = template.type_guard
+ if cactual.type_guard is not None:
+ cactual_ret_type = cactual.type_guard
+ res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction))
+
if param_spec is None:
# TODO: Erase template variables if it is generic?
if (
@@ -1008,34 +1067,6 @@
)
extra_tvars = True
- if not cactual_ps:
- max_prefix_len = len([k for k in cactual.arg_kinds if k in (ARG_POS, ARG_OPT)])
- prefix_len = min(prefix_len, max_prefix_len)
- res.append(
- Constraint(
- param_spec,
- neg_op(self.direction),
- Parameters(
- arg_types=cactual.arg_types[prefix_len:],
- arg_kinds=cactual.arg_kinds[prefix_len:],
- arg_names=cactual.arg_names[prefix_len:],
- variables=cactual.variables
- if not type_state.infer_polymorphic
- else [],
- ),
- )
- )
- else:
- if len(param_spec.prefix.arg_types) <= len(cactual_ps.prefix.arg_types):
- cactual_ps = cactual_ps.copy_modified(
- prefix=Parameters(
- arg_types=cactual_ps.prefix.arg_types[prefix_len:],
- arg_kinds=cactual_ps.prefix.arg_kinds[prefix_len:],
- arg_names=cactual_ps.prefix.arg_names[prefix_len:],
- )
- )
- res.append(Constraint(param_spec, neg_op(self.direction), cactual_ps))
-
# Compare prefixes as well
cactual_prefix = cactual.copy_modified(
arg_types=cactual.arg_types[:prefix_len],
@@ -1046,13 +1077,40 @@
infer_callable_arguments_constraints(prefix, cactual_prefix, self.direction)
)
- template_ret_type, cactual_ret_type = template.ret_type, cactual.ret_type
- if template.type_guard is not None:
- template_ret_type = template.type_guard
- if cactual.type_guard is not None:
- cactual_ret_type = cactual.type_guard
-
- res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction))
+ param_spec_target: Type | None = None
+ skip_imprecise = (
+ any(c.type_var == param_spec.id for c in res) and cactual.imprecise_arg_kinds
+ )
+ if not cactual_ps:
+ max_prefix_len = len([k for k in cactual.arg_kinds if k in (ARG_POS, ARG_OPT)])
+ prefix_len = min(prefix_len, max_prefix_len)
+ # This logic matches top-level callable constraint exception, if we managed
+ # to get other constraints for ParamSpec, don't infer one with imprecise kinds
+ if not skip_imprecise:
+ param_spec_target = Parameters(
+ arg_types=cactual.arg_types[prefix_len:],
+ arg_kinds=cactual.arg_kinds[prefix_len:],
+ arg_names=cactual.arg_names[prefix_len:],
+ variables=cactual.variables
+ if not type_state.infer_polymorphic
+ else [],
+ imprecise_arg_kinds=cactual.imprecise_arg_kinds,
+ )
+ else:
+ if (
+ len(param_spec.prefix.arg_types) <= len(cactual_ps.prefix.arg_types)
+ and not skip_imprecise
+ ):
+ param_spec_target = cactual_ps.copy_modified(
+ prefix=Parameters(
+ arg_types=cactual_ps.prefix.arg_types[prefix_len:],
+ arg_kinds=cactual_ps.prefix.arg_kinds[prefix_len:],
+ arg_names=cactual_ps.prefix.arg_names[prefix_len:],
+ imprecise_arg_kinds=cactual_ps.prefix.imprecise_arg_kinds,
+ )
+ )
+ if param_spec_target is not None:
+ res.append(Constraint(param_spec, neg_op(self.direction), param_spec_target))
if extra_tvars:
for c in res:
c.extra_tvars += cactual.variables
diff --git a/mypy/expandtype.py b/mypy/expandtype.py
index dc3dae6..7168d7c 100644
--- a/mypy/expandtype.py
+++ b/mypy/expandtype.py
@@ -336,6 +336,7 @@
arg_types=self.expand_types(t.arg_types),
ret_type=t.ret_type.accept(self),
type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None),
+ imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds),
)
elif isinstance(repl, ParamSpecType):
# We're substituting one ParamSpec for another; this can mean that the prefix
@@ -352,6 +353,7 @@
arg_names=t.arg_names[:-2] + prefix.arg_names + t.arg_names[-2:],
ret_type=t.ret_type.accept(self),
from_concatenate=t.from_concatenate or bool(repl.prefix.arg_types),
+ imprecise_arg_kinds=(t.imprecise_arg_kinds or prefix.imprecise_arg_kinds),
)
var_arg = t.var_arg()
diff --git a/mypy/infer.py b/mypy/infer.py
index f340879..ba4a1d2 100644
--- a/mypy/infer.py
+++ b/mypy/infer.py
@@ -33,6 +33,7 @@
callee_type: CallableType,
arg_types: Sequence[Type | None],
arg_kinds: list[ArgKind],
+ arg_names: Sequence[str | None] | None,
formal_to_actual: list[list[int]],
context: ArgumentInferContext,
strict: bool = True,
@@ -53,7 +54,7 @@
"""
# Infer constraints.
constraints = infer_constraints_for_callable(
- callee_type, arg_types, arg_kinds, formal_to_actual, context
+ callee_type, arg_types, arg_kinds, arg_names, formal_to_actual, context
)
# Solve constraints.
diff --git a/mypy/types.py b/mypy/types.py
index 214978e..cf2c343 100644
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -1560,6 +1560,7 @@
# TODO: variables don't really belong here, but they are used to allow hacky support
# for forall . Foo[[x: T], T] by capturing generic callable with ParamSpec, see #15909
"variables",
+ "imprecise_arg_kinds",
)
def __init__(
@@ -1570,6 +1571,7 @@
*,
variables: Sequence[TypeVarLikeType] | None = None,
is_ellipsis_args: bool = False,
+ imprecise_arg_kinds: bool = False,
line: int = -1,
column: int = -1,
) -> None:
@@ -1582,6 +1584,7 @@
self.min_args = arg_kinds.count(ARG_POS)
self.is_ellipsis_args = is_ellipsis_args
self.variables = variables or []
+ self.imprecise_arg_kinds = imprecise_arg_kinds
def copy_modified(
self,
@@ -1591,6 +1594,7 @@
*,
variables: Bogus[Sequence[TypeVarLikeType]] = _dummy,
is_ellipsis_args: Bogus[bool] = _dummy,
+ imprecise_arg_kinds: Bogus[bool] = _dummy,
) -> Parameters:
return Parameters(
arg_types=arg_types if arg_types is not _dummy else self.arg_types,
@@ -1600,6 +1604,11 @@
is_ellipsis_args if is_ellipsis_args is not _dummy else self.is_ellipsis_args
),
variables=variables if variables is not _dummy else self.variables,
+ imprecise_arg_kinds=(
+ imprecise_arg_kinds
+ if imprecise_arg_kinds is not _dummy
+ else self.imprecise_arg_kinds
+ ),
)
# TODO: here is a lot of code duplication with Callable type, fix this.
@@ -1696,6 +1705,7 @@
"arg_kinds": [int(x.value) for x in self.arg_kinds],
"arg_names": self.arg_names,
"variables": [tv.serialize() for tv in self.variables],
+ "imprecise_arg_kinds": self.imprecise_arg_kinds,
}
@classmethod
@@ -1706,6 +1716,7 @@
[ArgKind(x) for x in data["arg_kinds"]],
data["arg_names"],
variables=[cast(TypeVarLikeType, deserialize_type(v)) for v in data["variables"]],
+ imprecise_arg_kinds=data["imprecise_arg_kinds"],
)
def __hash__(self) -> int:
@@ -1762,6 +1773,7 @@
"type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case).
"from_concatenate", # whether this callable is from a concatenate object
# (this is used for error messages)
+ "imprecise_arg_kinds",
"unpack_kwargs", # Was an Unpack[...] with **kwargs used to define this callable?
)
@@ -1786,6 +1798,7 @@
def_extras: dict[str, Any] | None = None,
type_guard: Type | None = None,
from_concatenate: bool = False,
+ imprecise_arg_kinds: bool = False,
unpack_kwargs: bool = False,
) -> None:
super().__init__(line, column)
@@ -1812,6 +1825,7 @@
self.special_sig = special_sig
self.from_type_type = from_type_type
self.from_concatenate = from_concatenate
+ self.imprecise_arg_kinds = imprecise_arg_kinds
if not bound_args:
bound_args = ()
self.bound_args = bound_args
@@ -1854,6 +1868,7 @@
def_extras: Bogus[dict[str, Any]] = _dummy,
type_guard: Bogus[Type | None] = _dummy,
from_concatenate: Bogus[bool] = _dummy,
+ imprecise_arg_kinds: Bogus[bool] = _dummy,
unpack_kwargs: Bogus[bool] = _dummy,
) -> CT:
modified = CallableType(
@@ -1879,6 +1894,11 @@
from_concatenate=(
from_concatenate if from_concatenate is not _dummy else self.from_concatenate
),
+ imprecise_arg_kinds=(
+ imprecise_arg_kinds
+ if imprecise_arg_kinds is not _dummy
+ else self.imprecise_arg_kinds
+ ),
unpack_kwargs=unpack_kwargs if unpack_kwargs is not _dummy else self.unpack_kwargs,
)
# Optimization: Only NewTypes are supported as subtypes since
@@ -2191,6 +2211,7 @@
"def_extras": dict(self.def_extras),
"type_guard": self.type_guard.serialize() if self.type_guard is not None else None,
"from_concatenate": self.from_concatenate,
+ "imprecise_arg_kinds": self.imprecise_arg_kinds,
"unpack_kwargs": self.unpack_kwargs,
}
@@ -2214,6 +2235,7 @@
deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None
),
from_concatenate=data["from_concatenate"],
+ imprecise_arg_kinds=data["imprecise_arg_kinds"],
unpack_kwargs=data["unpack_kwargs"],
)
diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test
index 257fb92..ed1d59b 100644
--- a/test-data/unit/check-parameter-specification.test
+++ b/test-data/unit/check-parameter-specification.test
@@ -239,7 +239,6 @@
f(g, 'x', y='x') # E: Argument 2 to "f" has incompatible type "str"; expected "int"
f(g, 1, y=1) # E: Argument "y" to "f" has incompatible type "int"; expected "str"
f(g) # E: Missing positional arguments "x", "y" in call to "f"
-
[builtins fixtures/dict.pyi]
[case testParamSpecSpecialCase]
@@ -415,14 +414,19 @@
T = TypeVar('T')
# Similar to atexit.register
-def register(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[P, T]: ... # N: "register" defined here
+def register(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[P, T]: ...
def f(x: int) -> None: pass
+def g(x: int, y: str) -> None: pass
reveal_type(register(lambda: f(1))) # N: Revealed type is "def ()"
-reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (x: Any)"
-register(lambda x: f(x)) # E: Missing positional argument "x" in call to "register"
-register(lambda x: f(x), y=1) # E: Unexpected keyword argument "y" for "register"
+reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (x: Literal[1]?)"
+register(lambda x: f(x)) # E: Cannot infer type of lambda \
+ # E: Argument 1 to "register" has incompatible type "Callable[[Any], None]"; expected "Callable[[], None]"
+register(lambda x: f(x), y=1) # E: Argument 1 to "register" has incompatible type "Callable[[Arg(int, 'x')], None]"; expected "Callable[[Arg(int, 'y')], None]"
+reveal_type(register(lambda x: f(x), 1)) # N: Revealed type is "def (Literal[1]?)"
+reveal_type(register(lambda x, y: g(x, y), 1, "a")) # N: Revealed type is "def (Literal[1]?, Literal['a']?)"
+reveal_type(register(lambda x, y: g(x, y), 1, y="a")) # N: Revealed type is "def (Literal[1]?, y: Literal['a']?)"
[builtins fixtures/dict.pyi]
[case testParamSpecInvalidCalls]
@@ -909,8 +913,7 @@
reveal_type(A().func(f, 42)) # N: Revealed type is "builtins.int"
-# TODO: this should reveal `int`
-reveal_type(A().func(lambda x: x + x, 42)) # N: Revealed type is "Any"
+reveal_type(A().func(lambda x: x + x, 42)) # N: Revealed type is "builtins.int"
[builtins fixtures/paramspec.pyi]
[case testParamSpecConstraintOnOtherParamSpec]
@@ -1355,7 +1358,6 @@
class Some(Generic[P]):
def call(self, *args: P.args, **kwargs: P.kwargs): ...
-# TODO: this probably should be reported.
def call(*args: P.args, **kwargs: P.kwargs): ...
[builtins fixtures/paramspec.pyi]
@@ -1631,7 +1633,41 @@
dec(test_with_bound)(A()) # OK
[builtins fixtures/paramspec.pyi]
+[case testParamSpecArgumentParamInferenceRegular]
+from typing import TypeVar, Generic
+from typing_extensions import ParamSpec
+
+P = ParamSpec("P")
+class Foo(Generic[P]):
+ def call(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
+def test(*args: P.args, **kwargs: P.kwargs) -> Foo[P]: ...
+
+reveal_type(test(1, 2)) # N: Revealed type is "__main__.Foo[[Literal[1]?, Literal[2]?]]"
+reveal_type(test(x=1, y=2)) # N: Revealed type is "__main__.Foo[[x: Literal[1]?, y: Literal[2]?]]"
+ints = [1, 2, 3]
+reveal_type(test(*ints)) # N: Revealed type is "__main__.Foo[[*builtins.int]]"
+[builtins fixtures/paramspec.pyi]
+
+[case testParamSpecArgumentParamInferenceGeneric]
+# flags: --new-type-inference
+from typing import Callable, TypeVar
+from typing_extensions import ParamSpec
+
+P = ParamSpec("P")
+R = TypeVar("R")
+def call(f: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
+ return f(*args, **kwargs)
+
+T = TypeVar("T")
+def identity(x: T) -> T:
+ return x
+
+reveal_type(call(identity, 2)) # N: Revealed type is "builtins.int"
+y: int = call(identity, 2)
+[builtins fixtures/paramspec.pyi]
+
[case testParamSpecNestedApplyNoCrash]
+# flags: --new-type-inference
from typing import Callable, TypeVar
from typing_extensions import ParamSpec
@@ -1639,9 +1675,33 @@
T = TypeVar("T")
def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ...
-def test() -> None: ...
-# TODO: avoid this error, although it may be non-trivial.
-apply(apply, test) # E: Argument 2 to "apply" has incompatible type "Callable[[], None]"; expected "Callable[P, T]"
+def test() -> int: ...
+reveal_type(apply(apply, test)) # N: Revealed type is "builtins.int"
+[builtins fixtures/paramspec.pyi]
+
+[case testParamSpecNestedApplyPosVsNamed]
+from typing import Callable, TypeVar
+from typing_extensions import ParamSpec
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: ...
+def test(x: int) -> int: ...
+apply(apply, test, x=42) # OK
+apply(apply, test, 42) # Also OK (but requires some special casing)
+[builtins fixtures/paramspec.pyi]
+
+[case testParamSpecApplyPosVsNamedOptional]
+from typing import Callable, TypeVar
+from typing_extensions import ParamSpec
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+def apply(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: ...
+def test(x: str = ..., y: int = ...) -> int: ...
+apply(test, y=42) # OK
[builtins fixtures/paramspec.pyi]
[case testParamSpecPrefixSubtypingGenericInvalid]
diff --git a/test-data/unit/fixtures/paramspec.pyi b/test-data/unit/fixtures/paramspec.pyi
index 5e4b856..9b0089f 100644
--- a/test-data/unit/fixtures/paramspec.pyi
+++ b/test-data/unit/fixtures/paramspec.pyi
@@ -30,7 +30,8 @@
def __iter__(self) -> Iterator[T]: ...
class int:
- def __neg__(self) -> 'int': ...
+ def __neg__(self) -> int: ...
+ def __add__(self, other: int) -> int: ...
class bool(int): ...
class float: ...
diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test
index cd2afe2..c4c3a1d 100644
--- a/test-data/unit/typexport-basic.test
+++ b/test-data/unit/typexport-basic.test
@@ -727,7 +727,7 @@
class B:
a = None # type: A
[out]
-LambdaExpr(2) : def (B) -> A
+LambdaExpr(2) : def (x: B) -> A
MemberExpr(2) : A
NameExpr(2) : B
@@ -756,7 +756,7 @@
a = None # type: A
[builtins fixtures/list.pyi]
[out]
-LambdaExpr(2) : def (B) -> builtins.list[A]
+LambdaExpr(2) : def (x: B) -> builtins.list[A]
ListExpr(2) : builtins.list[A]
[case testLambdaAndHigherOrderFunction]
@@ -775,7 +775,7 @@
CallExpr(9) : builtins.list[B]
NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B]
CallExpr(10) : B
-LambdaExpr(10) : def (A) -> B
+LambdaExpr(10) : def (x: A) -> B
NameExpr(10) : def (a: A) -> B
NameExpr(10) : builtins.list[A]
NameExpr(10) : A
@@ -795,7 +795,7 @@
[builtins fixtures/list.pyi]
[out]
NameExpr(10) : def (f: def (A) -> builtins.list[B], a: builtins.list[A]) -> builtins.list[B]
-LambdaExpr(11) : def (A) -> builtins.list[B]
+LambdaExpr(11) : def (x: A) -> builtins.list[B]
ListExpr(11) : builtins.list[B]
NameExpr(11) : def (a: A) -> B
NameExpr(11) : builtins.list[A]
@@ -817,7 +817,7 @@
-- context. Perhaps just fail instead?
CallExpr(7) : builtins.list[Any]
NameExpr(7) : def (f: builtins.list[def (A) -> Any], a: builtins.list[A]) -> builtins.list[Any]
-LambdaExpr(8) : def (A) -> A
+LambdaExpr(8) : def (x: A) -> A
ListExpr(8) : builtins.list[def (A) -> Any]
NameExpr(8) : A
NameExpr(9) : builtins.list[A]
@@ -838,7 +838,7 @@
[out]
CallExpr(9) : builtins.list[B]
NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B]
-LambdaExpr(10) : def (A) -> B
+LambdaExpr(10) : def (x: A) -> B
MemberExpr(10) : B
NameExpr(10) : A
NameExpr(11) : builtins.list[A]
@@ -860,7 +860,7 @@
CallExpr(9) : builtins.list[B]
NameExpr(9) : def (f: def (A) -> B, a: builtins.list[A]) -> builtins.list[B]
NameExpr(10) : builtins.list[A]
-LambdaExpr(11) : def (A) -> B
+LambdaExpr(11) : def (x: A) -> B
MemberExpr(11) : B
NameExpr(11) : A
@@ -1212,7 +1212,7 @@
[builtins fixtures/list.pyi]
[out]
NameExpr(8) : Overload(def (x: builtins.int, f: def (builtins.int) -> builtins.int), def (x: builtins.str, f: def (builtins.str) -> builtins.str))
-LambdaExpr(9) : def (builtins.int) -> builtins.int
+LambdaExpr(9) : def (x: builtins.int) -> builtins.int
NameExpr(9) : builtins.int
[case testExportOverloadArgTypeNested]
@@ -1231,10 +1231,10 @@
lambda x: x)
[builtins fixtures/list.pyi]
[out]
-LambdaExpr(9) : def (builtins.int) -> builtins.int
-LambdaExpr(10) : def (builtins.int) -> builtins.int
-LambdaExpr(12) : def (builtins.str) -> builtins.str
-LambdaExpr(13) : def (builtins.str) -> builtins.str
+LambdaExpr(9) : def (y: builtins.int) -> builtins.int
+LambdaExpr(10) : def (x: builtins.int) -> builtins.int
+LambdaExpr(12) : def (y: builtins.str) -> builtins.str
+LambdaExpr(13) : def (x: builtins.str) -> builtins.str
-- TODO
--