Skip to content

Commit c7f6d78

Browse files
committed
server and incremental
1 parent 65e356a commit c7f6d78

File tree

4 files changed

+91
-0
lines changed

4 files changed

+91
-0
lines changed

mypy/fixup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ def visit_instance(self, inst: Instance) -> None:
239239
a.accept(self)
240240
if inst.last_known_value is not None:
241241
inst.last_known_value.accept(self)
242+
if inst.extra_attrs:
243+
for v in inst.extra_attrs.attrs.values():
244+
v.accept(self)
242245

243246
def visit_type_alias_type(self, t: TypeAliasType) -> None:
244247
type_ref = t.type_ref

mypy/server/astdiff.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,11 +378,19 @@ def visit_deleted_type(self, typ: DeletedType) -> SnapshotItem:
378378
return snapshot_simple_type(typ)
379379

380380
def visit_instance(self, typ: Instance) -> SnapshotItem:
381+
if self.extra_attrs:
382+
extra_attrs = (
383+
tuple(sorted((k, self.visit(v)) for k, v in self.extra_attrs.attrs.items())),
384+
tuple(self.extra_attrs.immutable),
385+
)
386+
else:
387+
extra_attrs = ()
381388
return (
382389
"Instance",
383390
encode_optional_str(typ.type.fullname),
384391
snapshot_types(typ.args),
385392
("None",) if typ.last_known_value is None else snapshot_type(typ.last_known_value),
393+
extra_attrs,
386394
)
387395

388396
def visit_type_var(self, typ: TypeVarType) -> SnapshotItem:

mypy/types.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,23 @@ def copy(self) -> ExtraAttrs:
13561356
def __repr__(self) -> str:
13571357
return f"ExtraAttrs({self.attrs!r}, {self.immutable!r}, {self.mod_name!r})"
13581358

1359+
def serialize(self) -> JsonDict:
1360+
return {
1361+
".class": "ExtraAttrs",
1362+
"attrs": {k: v.serialize() for k, v in self.attrs.items()},
1363+
"immutable": list(self.immutable),
1364+
"mod_name": self.mod_name,
1365+
}
1366+
1367+
@classmethod
1368+
def deserialize(cls, data: JsonDict) -> ExtraAttrs:
1369+
assert data[".class"] == "ExtraAttrs"
1370+
return ExtraAttrs(
1371+
{k: deserialize_type(v) for k, v in data["attrs"].items()},
1372+
set(data["immutable"]),
1373+
data["mod_name"],
1374+
)
1375+
13591376

13601377
class Instance(ProperType):
13611378
"""An instance type of form C[T1, ..., Tn].
@@ -1468,6 +1485,7 @@ def serialize(self) -> JsonDict | str:
14681485
data["args"] = [arg.serialize() for arg in self.args]
14691486
if self.last_known_value is not None:
14701487
data["last_known_value"] = self.last_known_value.serialize()
1488+
data["extra_attrs"] = self.extra_attrs.serialize() if self.extra_attrs else None
14711489
return data
14721490

14731491
@classmethod
@@ -1486,6 +1504,8 @@ def deserialize(cls, data: JsonDict | str) -> Instance:
14861504
inst.type_ref = data["type_ref"] # Will be fixed up by fixup.py later.
14871505
if "last_known_value" in data:
14881506
inst.last_known_value = LiteralType.deserialize(data["last_known_value"])
1507+
if data.get("extra_attrs") is not None:
1508+
inst.extra_attrs = ExtraAttrs.deserialize(data["extra_attrs"])
14891509
return inst
14901510

14911511
def copy_modified(

test-data/unit/check-incremental.test

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6574,3 +6574,63 @@ class TheClass:
65746574
[out]
65756575
[out2]
65766576
tmp/a.py:3: note: Revealed type is "def (value: builtins.object) -> lib.TheClass.pyenum@6"
6577+
6578+
6579+
[case testIncrementalFunctoolsPartial]
6580+
import a
6581+
6582+
[file a.py]
6583+
from typing import Callable
6584+
from partial import p1, p2
6585+
6586+
p1(1, "a", 3) # OK
6587+
p1(1, "a", c=3) # OK
6588+
p1(1, b="a", c=3) # OK
6589+
6590+
reveal_type(p1)
6591+
6592+
def takes_callable_int(f: Callable[..., int]) -> None: ...
6593+
def takes_callable_str(f: Callable[..., str]) -> None: ...
6594+
takes_callable_int(p1)
6595+
takes_callable_str(p1)
6596+
6597+
p2("a") # OK
6598+
p2("a", 3) # OK
6599+
p2("a", c=3) # OK
6600+
p2(1, 3)
6601+
p2(1, "a", 3)
6602+
p2(a=1, b="a", c=3)
6603+
6604+
[file a.py.2]
6605+
from typing import Callable
6606+
from partial import p3
6607+
6608+
p3(1) # OK
6609+
p3(1, c=3) # OK
6610+
p3(a=1) # OK
6611+
p3(1, b="a", c=3) # OK, keywords can be clobbered
6612+
p3(1, 3)
6613+
6614+
[file partial.py]
6615+
from typing import Callable
6616+
import functools
6617+
6618+
def foo(a: int, b: str, c: int = 5) -> int: ...
6619+
6620+
p1 = functools.partial(foo)
6621+
p2 = functools.partial(foo, 1)
6622+
p3 = functools.partial(foo, b="a")
6623+
[builtins fixtures/dict.pyi]
6624+
[out]
6625+
tmp/a.py:8: note: Revealed type is "functools.partial[builtins.int]"
6626+
tmp/a.py:13: error: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]"
6627+
tmp/a.py:13: note: "partial[int].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], int]"
6628+
tmp/a.py:18: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
6629+
tmp/a.py:19: error: Too many arguments for "foo"
6630+
tmp/a.py:19: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
6631+
tmp/a.py:19: error: Argument 2 to "foo" has incompatible type "str"; expected "int"
6632+
tmp/a.py:20: error: Unexpected keyword argument "a" for "foo"
6633+
tmp/partial.py:4: note: "foo" defined here
6634+
[out2]
6635+
tmp/a.py:8: error: Too many positional arguments for "foo"
6636+
tmp/a.py:8: error: Argument 2 to "foo" has incompatible type "int"; expected "str"

0 commit comments

Comments
 (0)