Skip to content
Open
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
22 changes: 21 additions & 1 deletion mypy/solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint, infer_constraints, neg_op
from mypy.expandtype import expand_type
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
from mypy.join import join_type_list
from mypy.join import is_similar_params, join_type_list
from mypy.meet import meet_type_list, meet_types
from mypy.subtypes import is_subtype
from mypy.typeops import get_all_type_vars
from mypy.types import (
AnyType,
Instance,
NoneType,
Parameters,
ParamSpecType,
ProperType,
TupleType,
Expand Down Expand Up @@ -256,6 +257,13 @@ def _join_sorted_key(t: Type) -> int:
return 0


def _precise_parameter_constraint_target(target: Type) -> Parameters | None:
target = get_proper_type(target)
if isinstance(target, Parameters) and not target.imprecise_arg_kinds:
return target
return None


def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
"""Solve constraints by finding by using meets of upper bounds, and joins of lower bounds."""

Expand Down Expand Up @@ -288,6 +296,18 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
# Retain `None` when no bottoms were provided to avoid bogus `Never` inference.
bottom = UnionType.make_union(lowers)
else:
# Joining incompatible concrete ParamSpec lower bounds falls back to Any,
# but for precise call signatures this would silently erase the conflict.
precise_param_lowers = [
lower
for lower in (_precise_parameter_constraint_target(lower) for lower in lowers)
if lower is not None
]
if precise_param_lowers and not all(
is_similar_params(precise_param_lowers[0], lower) for lower in precise_param_lowers[1:]
):
return None

# The order of lowers is non-deterministic.
# We attempt to sort lowers because joins are non-associative. For instance:
# join(join(int, str), int | str) == join(object, int | str) == object
Expand Down
36 changes: 36 additions & 0 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -2556,6 +2556,42 @@ def fn(f: MiddlewareFactory[P]) -> Capture[P]: ...
reveal_type(fn(ServerErrorMiddleware)) # N: Revealed type is "__main__.Capture[[handler: builtins.str | None =, debug: builtins.bool =]]"
[builtins fixtures/paramspec.pyi]

[case testParamSpecProtocolInferenceRejectsConflictingMembers]
from typing import Generic, Protocol, TypeVar
from typing_extensions import ParamSpec

P = ParamSpec("P")
T = TypeVar("T")

class Context: pass

class Namer(Protocol[P]):
def name_for(self, *args: P.args, **kwargs: P.kwargs) -> str: ...
def execute_on(self, ctx: Context, *args: P.args, **kwargs: P.kwargs) -> None: ...

class Impl0:
def name_for(self, x: int, y: str) -> str: ...
def execute_on(self, ctx: Context, x: int, y: str) -> None: ...

class Impl1:
def name_for(self, y: str) -> str: ...
def execute_on(self, ctx: Context, x: int, y: str) -> None: ...

class UseImplFirst(Generic[P, T]):
def __init__(self, impl: T, *args: P.args, **kwargs: P.kwargs) -> None: ...
def __call__(self: "UseImplFirst[P, Namer[P]]") -> None: ...

def use_impl_second(impl: Namer[P], *args: P.args, **kwargs: P.kwargs) -> None: ...
def use_impl_third(wrapper: UseImplFirst[P, Namer[P]]) -> None: ...

ok: Namer[[int, str]] = Impl0()
use_impl_second(Impl0(), 0, "0")
use_impl_third(UseImplFirst(Impl0(), 0, "0"))

use_impl_second(Impl1(), 1, "1") # E: Cannot infer value of type parameter "P" of "use_impl_second"
use_impl_third(UseImplFirst(Impl1(), 1, "1")) # E: Cannot infer value of type parameter "P" of "use_impl_third"
[builtins fixtures/paramspec.pyi]

[case testRunParamSpecDuplicateArgsKwargs]
from typing_extensions import ParamSpec, Concatenate
from typing import Callable, Union
Expand Down
Loading