Skip to content

Commit c9a5d56

Browse files
authored
Preserve the name of first argument in methods when serializing (#5109)
This is useful to give nicer error messages after serialization. This is a step towards #4772 Unblocks #1412 (much faster pythoneval tests).
1 parent f639e1b commit c9a5d56

File tree

3 files changed

+60
-1
lines changed

3 files changed

+60
-1
lines changed

mypy/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,13 @@ def [T <: int] f(self, x: int, y: T) -> None
12541254
s = ', ' + s
12551255
s = definition_args[0] + s
12561256
s = '{}({})'.format(tp.definition.name(), s)
1257+
elif tp.name:
1258+
first_arg = tp.def_extras.get('first_arg')
1259+
if first_arg:
1260+
if s:
1261+
s = ', ' + s
1262+
s = first_arg + s
1263+
s = '{}({})'.format(tp.name.split()[0], s) # skip "of Class" part
12571264
else:
12581265
s = '({})'.format(s)
12591266

mypy/types.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from mypy import experiments
1414
from mypy.nodes import (
1515
INVARIANT, SymbolNode, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT,
16+
FuncDef
1617
)
1718
from mypy.sharedparse import argument_elide_name
1819
from mypy.util import IdMapper
@@ -676,6 +677,8 @@ class CallableType(FunctionLike):
676677
# instantiation?
677678
'bound_args', # Bound type args, mostly unused but may be useful for
678679
# tools that consume mypy ASTs
680+
'def_extras', # Information about original definition we want to serialize.
681+
# This is used for more detailed error messages.
679682
)
680683

681684
def __init__(self,
@@ -695,6 +698,7 @@ def __init__(self,
695698
special_sig: Optional[str] = None,
696699
from_type_type: bool = False,
697700
bound_args: Sequence[Optional[Type]] = (),
701+
def_extras: Optional[Dict[str, Any]] = None,
698702
) -> None:
699703
super().__init__(line, column)
700704
assert len(arg_types) == len(arg_kinds) == len(arg_names)
@@ -721,6 +725,18 @@ def __init__(self,
721725
if not bound_args:
722726
bound_args = ()
723727
self.bound_args = bound_args
728+
if def_extras:
729+
self.def_extras = def_extras
730+
elif isinstance(definition, FuncDef):
731+
# This information would be lost if we don't have definition
732+
# after serialization, but it is useful in error messages.
733+
# TODO: decide how to add more info here (file, line, column)
734+
# without changing interface hash.
735+
self.def_extras = {'first_arg': definition.arg_names[0]
736+
if definition.arg_names and definition.info and
737+
not definition.is_static else None}
738+
else:
739+
self.def_extras = {}
724740

725741
def copy_modified(self,
726742
arg_types: List[Type] = _dummy,
@@ -736,7 +752,8 @@ def copy_modified(self,
736752
is_ellipsis_args: bool = _dummy,
737753
special_sig: Optional[str] = _dummy,
738754
from_type_type: bool = _dummy,
739-
bound_args: List[Optional[Type]] = _dummy) -> 'CallableType':
755+
bound_args: List[Optional[Type]] = _dummy,
756+
def_extras: Dict[str, Any] = _dummy) -> 'CallableType':
740757
return CallableType(
741758
arg_types=arg_types if arg_types is not _dummy else self.arg_types,
742759
arg_kinds=arg_kinds if arg_kinds is not _dummy else self.arg_kinds,
@@ -755,6 +772,7 @@ def copy_modified(self,
755772
special_sig=special_sig if special_sig is not _dummy else self.special_sig,
756773
from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type,
757774
bound_args=bound_args if bound_args is not _dummy else self.bound_args,
775+
def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras),
758776
)
759777

760778
def is_type_obj(self) -> bool:
@@ -907,6 +925,7 @@ def serialize(self) -> JsonDict:
907925
'is_classmethod_class': self.is_classmethod_class,
908926
'bound_args': [(None if t is None else t.serialize())
909927
for t in self.bound_args],
928+
'def_extras': dict(self.def_extras),
910929
}
911930

912931
@classmethod
@@ -925,6 +944,7 @@ def deserialize(cls, data: JsonDict) -> 'CallableType':
925944
is_classmethod_class=data['is_classmethod_class'],
926945
bound_args=[(None if t is None else deserialize_type(t))
927946
for t in data['bound_args']],
947+
def_extras=data['def_extras']
928948
)
929949

930950

test-data/unit/check-incremental.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4378,3 +4378,35 @@ from d import k
43784378
[file t.py.2]
43794379
from d import k
43804380
# dummy change
4381+
4382+
[case testCachedBadProtocolNote]
4383+
import b
4384+
[file a.py]
4385+
from mypy_extensions import TypedDict
4386+
Point = TypedDict('Point', {'x': int, 'y': int})
4387+
[file b.py]
4388+
from typing import Iterable
4389+
from a import Point
4390+
p: Point
4391+
it: Iterable[int] = p
4392+
[file b.py.2]
4393+
from typing import Iterable
4394+
from a import Point
4395+
p: Point
4396+
it: Iterable[int] = p # change
4397+
[typing fixtures/typing-full.pyi]
4398+
[builtins fixtures/dict.pyi]
4399+
[out]
4400+
tmp/b.py:4: error: Incompatible types in assignment (expression has type "Point", variable has type "Iterable[int]")
4401+
tmp/b.py:4: note: Following member(s) of "Point" have conflicts:
4402+
tmp/b.py:4: note: Expected:
4403+
tmp/b.py:4: note: def __iter__(self) -> Iterator[int]
4404+
tmp/b.py:4: note: Got:
4405+
tmp/b.py:4: note: def __iter__(self) -> Iterator[str]
4406+
[out2]
4407+
tmp/b.py:4: error: Incompatible types in assignment (expression has type "Point", variable has type "Iterable[int]")
4408+
tmp/b.py:4: note: Following member(s) of "Point" have conflicts:
4409+
tmp/b.py:4: note: Expected:
4410+
tmp/b.py:4: note: def __iter__(self) -> Iterator[int]
4411+
tmp/b.py:4: note: Got:
4412+
tmp/b.py:4: note: def __iter__(self) -> Iterator[str]

0 commit comments

Comments
 (0)