diff --git a/mypy/messages.py b/mypy/messages.py index e14fe64f3528..68f5d3c9648c 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1254,6 +1254,13 @@ def [T <: int] f(self, x: int, y: T) -> None s = ', ' + s s = definition_args[0] + s s = '{}({})'.format(tp.definition.name(), s) + elif tp.name: + first_arg = tp.def_extras.get('first_arg') + if first_arg: + if s: + s = ', ' + s + s = first_arg + s + s = '{}({})'.format(tp.name.split()[0], s) # skip "of Class" part else: s = '({})'.format(s) diff --git a/mypy/types.py b/mypy/types.py index 43015fbfe11a..9330d176d629 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -13,6 +13,7 @@ from mypy import experiments from mypy.nodes import ( INVARIANT, SymbolNode, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT, + FuncDef ) from mypy.sharedparse import argument_elide_name from mypy.util import IdMapper @@ -676,6 +677,8 @@ class CallableType(FunctionLike): # instantiation? 'bound_args', # Bound type args, mostly unused but may be useful for # tools that consume mypy ASTs + 'def_extras', # Information about original definition we want to serialize. + # This is used for more detailed error messages. ) def __init__(self, @@ -695,6 +698,7 @@ def __init__(self, special_sig: Optional[str] = None, from_type_type: bool = False, bound_args: Sequence[Optional[Type]] = (), + def_extras: Optional[Dict[str, Any]] = None, ) -> None: super().__init__(line, column) assert len(arg_types) == len(arg_kinds) == len(arg_names) @@ -721,6 +725,18 @@ def __init__(self, if not bound_args: bound_args = () self.bound_args = bound_args + if def_extras: + self.def_extras = def_extras + elif isinstance(definition, FuncDef): + # This information would be lost if we don't have definition + # after serialization, but it is useful in error messages. + # TODO: decide how to add more info here (file, line, column) + # without changing interface hash. + self.def_extras = {'first_arg': definition.arg_names[0] + if definition.arg_names and definition.info and + not definition.is_static else None} + else: + self.def_extras = {} def copy_modified(self, arg_types: List[Type] = _dummy, @@ -736,7 +752,8 @@ def copy_modified(self, is_ellipsis_args: bool = _dummy, special_sig: Optional[str] = _dummy, from_type_type: bool = _dummy, - bound_args: List[Optional[Type]] = _dummy) -> 'CallableType': + bound_args: List[Optional[Type]] = _dummy, + def_extras: Dict[str, Any] = _dummy) -> 'CallableType': return CallableType( arg_types=arg_types if arg_types is not _dummy else self.arg_types, arg_kinds=arg_kinds if arg_kinds is not _dummy else self.arg_kinds, @@ -755,6 +772,7 @@ def copy_modified(self, special_sig=special_sig if special_sig is not _dummy else self.special_sig, from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type, bound_args=bound_args if bound_args is not _dummy else self.bound_args, + def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras), ) def is_type_obj(self) -> bool: @@ -907,6 +925,7 @@ def serialize(self) -> JsonDict: 'is_classmethod_class': self.is_classmethod_class, 'bound_args': [(None if t is None else t.serialize()) for t in self.bound_args], + 'def_extras': dict(self.def_extras), } @classmethod @@ -925,6 +944,7 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': is_classmethod_class=data['is_classmethod_class'], bound_args=[(None if t is None else deserialize_type(t)) for t in data['bound_args']], + def_extras=data['def_extras'] ) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index bcdaaa0395b1..92edf2e900c7 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -4378,3 +4378,35 @@ from d import k [file t.py.2] from d import k # dummy change + +[case testCachedBadProtocolNote] +import b +[file a.py] +from mypy_extensions import TypedDict +Point = TypedDict('Point', {'x': int, 'y': int}) +[file b.py] +from typing import Iterable +from a import Point +p: Point +it: Iterable[int] = p +[file b.py.2] +from typing import Iterable +from a import Point +p: Point +it: Iterable[int] = p # change +[typing fixtures/typing-full.pyi] +[builtins fixtures/dict.pyi] +[out] +tmp/b.py:4: error: Incompatible types in assignment (expression has type "Point", variable has type "Iterable[int]") +tmp/b.py:4: note: Following member(s) of "Point" have conflicts: +tmp/b.py:4: note: Expected: +tmp/b.py:4: note: def __iter__(self) -> Iterator[int] +tmp/b.py:4: note: Got: +tmp/b.py:4: note: def __iter__(self) -> Iterator[str] +[out2] +tmp/b.py:4: error: Incompatible types in assignment (expression has type "Point", variable has type "Iterable[int]") +tmp/b.py:4: note: Following member(s) of "Point" have conflicts: +tmp/b.py:4: note: Expected: +tmp/b.py:4: note: def __iter__(self) -> Iterator[int] +tmp/b.py:4: note: Got: +tmp/b.py:4: note: def __iter__(self) -> Iterator[str]