Skip to content
Merged
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
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6435,7 +6435,7 @@ def visit_type_var(self, t: TypeVarType) -> bool:

def visit_param_spec(self, t: ParamSpecType) -> bool:
default = [t.default] if t.has_default() else []
return self.query_types([t.upper_bound, *default])
return self.query_types([t.upper_bound, *default, t.prefix])

def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool:
default = [t.default] if t.has_default() else []
Expand Down
1 change: 1 addition & 0 deletions mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
return t

def visit_param_spec(self, t: ParamSpecType) -> Type:
# TODO: we should probably preserve prefix here.
if self.erase_id is None or self.erase_id(t.id):
return self.replacement
return t
Expand Down
1 change: 1 addition & 0 deletions mypy/fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ def visit_type_var(self, tvt: TypeVarType) -> None:
def visit_param_spec(self, p: ParamSpecType) -> None:
p.upper_bound.accept(self)
p.default.accept(self)
p.prefix.accept(self)

def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
t.tuple_fallback.accept(self)
Expand Down
1 change: 1 addition & 0 deletions mypy/indirection.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def visit_type_var(self, t: types.TypeVarType) -> None:
def visit_param_spec(self, t: types.ParamSpecType) -> None:
self._visit(t.upper_bound)
self._visit(t.default)
self._visit(t.prefix)

def visit_type_var_tuple(self, t: types.TypeVarTupleType) -> None:
self._visit(t.upper_bound)
Expand Down
1 change: 1 addition & 0 deletions mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ def visit_param_spec(self, typ: ParamSpecType) -> SnapshotItem:
typ.flavor,
snapshot_type(typ.upper_bound),
snapshot_type(typ.default),
snapshot_type(typ.prefix),
)

def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem:
Expand Down
1 change: 1 addition & 0 deletions mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ def visit_type_var(self, typ: TypeVarType) -> None:
def visit_param_spec(self, typ: ParamSpecType) -> None:
typ.upper_bound.accept(self)
typ.default.accept(self)
typ.prefix.accept(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should NodeReplaceVisitor above do the same? More importantly, why isn't its process_param_spec_def actually ever called (as well as process_type_var_tuple_def)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leads to #14872 where process_param_spec_def was added, but apparently not connected to anything? It should probably be...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParamSpec definitions cannot have prefixes, it is purely mypy-internal thing to represent Concatenate in the type system. Also I noticed that process_param_spec_def() and process_type_var_tuple_def() are not called anywhere. Probably they should be called from visit_class_def(). But this is definitely a topic for a separate issue/PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I remember what a PSpec prefix means:) I just wasn't able to make sure that process_param_spec_def is actually called on definitions and not on other ParamSpec occurrences


def visit_type_var_tuple(self, typ: TypeVarTupleType) -> None:
typ.upper_bound.accept(self)
Expand Down
17 changes: 5 additions & 12 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,10 +1037,8 @@ def visit_type_var(self, typ: TypeVarType) -> list[str]:
triggers = []
if typ.fullname:
triggers.append(make_trigger(typ.fullname))
if typ.upper_bound:
triggers.extend(self.get_type_triggers(typ.upper_bound))
if typ.default:
triggers.extend(self.get_type_triggers(typ.default))
triggers.extend(self.get_type_triggers(typ.upper_bound))
triggers.extend(self.get_type_triggers(typ.default))
for val in typ.values:
triggers.extend(self.get_type_triggers(val))
return triggers
Expand All @@ -1049,22 +1047,17 @@ def visit_param_spec(self, typ: ParamSpecType) -> list[str]:
triggers = []
if typ.fullname:
triggers.append(make_trigger(typ.fullname))
if typ.upper_bound:
triggers.extend(self.get_type_triggers(typ.upper_bound))
if typ.default:
triggers.extend(self.get_type_triggers(typ.default))
triggers.extend(self.get_type_triggers(typ.upper_bound))
triggers.extend(self.get_type_triggers(typ.default))
triggers.extend(self.get_type_triggers(typ.prefix))
return triggers

def visit_type_var_tuple(self, typ: TypeVarTupleType) -> list[str]:
triggers = []
if typ.fullname:
triggers.append(make_trigger(typ.fullname))
if typ.upper_bound:
triggers.extend(self.get_type_triggers(typ.upper_bound))
if typ.default:
triggers.extend(self.get_type_triggers(typ.default))
triggers.extend(self.get_type_triggers(typ.upper_bound))
triggers.extend(self.get_type_triggers(typ.default))
return triggers

def visit_unpack_type(self, typ: UnpackType) -> list[str]:
Expand Down
2 changes: 1 addition & 1 deletion mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def visit_type_var(self, t: TypeVarType, /) -> bool:
return self.query_types([t.upper_bound, t.default] + t.values)

def visit_param_spec(self, t: ParamSpecType, /) -> bool:
return self.query_types([t.upper_bound, t.default])
return self.query_types([t.upper_bound, t.default, t.prefix])

def visit_type_var_tuple(self, t: TypeVarTupleType, /) -> bool:
return self.query_types([t.upper_bound, t.default])
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2615,7 +2615,7 @@ def visit_type_var(self, t: TypeVarType) -> None:
self.process_types([t.upper_bound, t.default] + t.values)

def visit_param_spec(self, t: ParamSpecType) -> None:
self.process_types([t.upper_bound, t.default])
self.process_types([t.upper_bound, t.default, t.prefix])

def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
self.process_types([t.upper_bound, t.default])
Expand Down
1 change: 1 addition & 0 deletions mypy/typetraverser.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def visit_type_var(self, t: TypeVarType, /) -> None:
t.default.accept(self)

def visit_param_spec(self, t: ParamSpecType, /) -> None:
# TODO: do we need to traverse prefix here?
t.default.accept(self)

def visit_parameters(self, t: Parameters, /) -> None:
Expand Down
67 changes: 67 additions & 0 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -6897,3 +6897,70 @@ import does_not_exist
[builtins fixtures/ops.pyi]
[out]
[out2]

[case testIncrementalNoCrashOnParamSpecPrefixUpdateMethod]
import impl
[file impl.py]
from typing_extensions import ParamSpec
from lib import Sub

P = ParamSpec("P")
class Impl(Sub[P]):
def test(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.meth(1, *args, **kwargs)

[file impl.py.2]
from typing_extensions import ParamSpec
from lib import Sub

P = ParamSpec("P")
class Impl(Sub[P]):
def test(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.meth("no", *args, **kwargs)

[file lib.py]
from typing import Generic
from typing_extensions import ParamSpec, Concatenate

P = ParamSpec("P")
class Base(Generic[P]):
def meth(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
class Sub(Base[Concatenate[int, P]]): ...
[builtins fixtures/paramspec.pyi]
[out]
[out2]
tmp/impl.py:7: error: Argument 1 to "meth" of "Base" has incompatible type "str"; expected "int"

[case testIncrementalNoCrashOnParamSpecPrefixUpdateMethodAlias]
import impl
[file impl.py]
from typing_extensions import ParamSpec
from lib import Sub

P = ParamSpec("P")
class Impl(Sub[P]):
def test(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.alias(1, *args, **kwargs)

[file impl.py.2]
from typing_extensions import ParamSpec
from lib import Sub

P = ParamSpec("P")
class Impl(Sub[P]):
def test(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.alias("no", *args, **kwargs)

[file lib.py]
from typing import Generic
from typing_extensions import ParamSpec, Concatenate

P = ParamSpec("P")
class Base(Generic[P]):
def meth(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
alias = meth
class Sub(Base[Concatenate[int, P]]): ...
[builtins fixtures/paramspec.pyi]
[out]
[out2]
tmp/impl.py:7: error: Argument 1 has incompatible type "str"; expected "int"
33 changes: 33 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -11485,3 +11485,36 @@ class A:
[out]
==
main:3: error: Too few arguments

[case testFineGrainedParamSpecPrefixUpdateMethod]
import impl
[file impl.py]
from typing_extensions import ParamSpec
from lib import Sub

P = ParamSpec("P")
class Impl(Sub[P]):
def test(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.meth(1, *args, **kwargs)

[file lib.py]
from typing import Generic
from typing_extensions import ParamSpec, Concatenate

P = ParamSpec("P")
class Base(Generic[P]):
def meth(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
class Sub(Base[Concatenate[int, P]]): ...

[file lib.py.2]
from typing import Generic
from typing_extensions import ParamSpec, Concatenate

P = ParamSpec("P")
class Base(Generic[P]):
def meth(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
class Sub(Base[Concatenate[str, P]]): ...
[builtins fixtures/paramspec.pyi]
[out]
==
impl.py:7: error: Argument 1 to "meth" of "Base" has incompatible type "int"; expected "str"
Loading