From a9b4a8c8b1822094f3dac032b92cb4bdeaf1335d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Perrin?= Date: Thu, 10 Mar 2022 10:26:00 +0000 Subject: [PATCH 1/4] Test for undefined FuncDef.arguments after deserialize from cache --- test-data/unit/check-modules.test | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 67767a9114e1..fc9ddedecc69 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -3190,3 +3190,35 @@ from dir1 import * from .test2 import * [file dir1/test2.py] from test1 import aaaa # E: Module "test1" has no attribute "aaaa" + +[case testIncompatibleOverrideFromCachedModuleIncremental] +import b +[file a.py] +class Foo: + def frobnicate(self, *args, **kwargs): pass +[file b.py] +from a import Foo +class Bar(Foo): + def frobnicate(self) -> None: pass +[file b.py.2] +from a import Foo +class Bar(Foo): + def frobnicate(self, *args) -> None: pass +[file b.py.3] +from a import Foo +class Bar(Foo): + def frobnicate(self, *args) -> None: pass # type: ignore[override] # I know +[builtins fixtures/tuple.pyi] +[builtins fixtures/dict.pyi] +[out1] +tmp/b.py:3: error: Signature of "frobnicate" incompatible with supertype "Foo" +tmp/b.py:3: note: Superclass: +tmp/b.py:3: note: def frobnicate(self, *args: Any, **kwargs: Any) -> Any +tmp/b.py:3: note: Subclass: +tmp/b.py:3: note: def frobnicate(self) -> None +[out2] +tmp/b.py:3: error: Signature of "frobnicate" incompatible with supertype "Foo" +tmp/b.py:3: note: Superclass: +tmp/b.py:3: note: def frobnicate(self, *args: Any, **kwargs: Any) -> Any +tmp/b.py:3: note: Subclass: +tmp/b.py:3: note: def frobnicate(self, *args: Any) -> None From 93fb5d5336115ef64d0904316e7f6ddbc8572bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Perrin?= Date: Thu, 10 Mar 2022 10:28:16 +0000 Subject: [PATCH 2/4] Do not delete FuncItem.arguments on deserialize When FuncDef is deserialized we don't reconstruct the arguments. Previous code was deleting the attribute, leading to #11899; instead, always set the attribute, maybe to the empty list, and get argument names from arg_names if arguments is empty. --- mypy/messages.py | 6 ++++-- mypy/nodes.py | 5 ++--- mypy/types.py | 17 +++++++---------- mypyc/irbuild/mapper.py | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 1d6641c00a61..84ba375522a3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1978,10 +1978,12 @@ def [T <: int] f(self, x: int, y: T) -> None s += ' = ...' # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list - if isinstance(tp.definition, FuncDef) and tp.definition.name is not None: + if isinstance(tp.definition, FuncDef) and \ + tp.definition.name is not None and \ + tp.definition.arguments: definition_args = [arg.variable.name for arg in tp.definition.arguments] if definition_args and tp.arg_names != definition_args \ - and len(definition_args) > 0 and definition_args[0]: + and len(definition_args) > 0 and definition_args[0]: if s: s = ', ' + s s = definition_args[0] + s diff --git a/mypy/nodes.py b/mypy/nodes.py index abc8666e390d..f3e7fef34749 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -649,7 +649,7 @@ def set_line(self, class FuncItem(FuncBase): """Base class for nodes usable as overloaded function items.""" - __slots__ = ('arguments', # Note that can be None if deserialized (type is a lie!) + __slots__ = ('arguments', # Note that can be empty if deserialized 'arg_names', # Names of arguments 'arg_kinds', # Kinds of arguments 'min_args', # Minimum number of arguments @@ -665,7 +665,7 @@ class FuncItem(FuncBase): 'expanded', # Variants of function with type variables with values expanded ) - __deletable__ = ('arguments', 'max_pos', 'min_args') + __deletable__ = ('max_pos', 'min_args') def __init__(self, arguments: Optional[List[Argument]] = None, @@ -780,7 +780,6 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef': ret.arg_names = data['arg_names'] ret.arg_kinds = [ArgKind(x) for x in data['arg_kinds']] # Leave these uninitialized so that future uses will trigger an error - del ret.arguments del ret.max_pos del ret.min_args return ret diff --git a/mypy/types.py b/mypy/types.py index f0f7add2d92f..dc78fdbdd931 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1524,16 +1524,13 @@ def __init__(self, # 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.arguments[0].variable.name - if (getattr(definition, 'arguments', None) - and definition.arg_names - and definition.info - and not definition.is_static) - else None - ), - } + first_arg: Optional[str] = None + if definition.info and not definition.is_static: + if definition.arguments: + first_arg = definition.arguments[0].variable.name + elif definition.arg_names: + first_arg = definition.arg_names[0] + self.def_extras = {'first_arg': first_arg} else: self.def_extras = {} self.type_guard = type_guard diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 576eacc141df..181479066b7b 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -145,7 +145,7 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature: # deserialized FuncDef that lacks arguments. We won't ever # need to use those inside of a FuncIR, so we just make up # some crap. - if hasattr(fdef, 'arguments'): + if fdef.arguments: arg_names = [arg.variable.name for arg in fdef.arguments] else: arg_names = [name or '' for name in fdef.arg_names] From 394075818c4987b8a8b4b5a1866b5ee36ba14516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Perrin?= Date: Mon, 23 May 2022 12:37:44 +0100 Subject: [PATCH 3/4] Better format complex conditional --- mypy/messages.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 84ba375522a3..4bafe85da0fc 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1978,12 +1978,12 @@ def [T <: int] f(self, x: int, y: T) -> None s += ' = ...' # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list - if isinstance(tp.definition, FuncDef) and \ - tp.definition.name is not None and \ - tp.definition.arguments: + if (isinstance(tp.definition, FuncDef) and + tp.definition.name is not None and + tp.definition.arguments): definition_args = [arg.variable.name for arg in tp.definition.arguments] - if definition_args and tp.arg_names != definition_args \ - and len(definition_args) > 0 and definition_args[0]: + if (tp.arg_names != definition_args and + definition_args[0]): if s: s = ', ' + s s = definition_args[0] + s From 85a8a6925c119646b96ef0e1ce7a1adf9f421a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Perrin?= Date: Mon, 30 May 2022 10:48:31 +0100 Subject: [PATCH 4/4] [mypyc] Mapper.fdef_to_sig: update comment --- mypyc/irbuild/mapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 181479066b7b..9c59104fd7ce 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -142,9 +142,9 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature: # the sole way that FuncDecl arguments are tracked. This is # generally fine except in some cases (like for computing # init_sig) we need to produce FuncSignatures from a - # deserialized FuncDef that lacks arguments. We won't ever - # need to use those inside of a FuncIR, so we just make up - # some crap. + # deserialized FuncDef where arguments is the empty list. We + # won't ever need to use those inside of a FuncIR, so we just + # make up some crap. if fdef.arguments: arg_names = [arg.variable.name for arg in fdef.arguments] else: