Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fperrin funcdef arguments optional #3

Closed
wants to merge 2 commits into from
Closed
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: 2 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
arg_type = self.named_generic_type('builtins.dict',
[self.str_type(),
arg_type])
assert item.arguments is not None
item.arguments[i].variable.type = arg_type

# Type check initialization expressions.
Expand Down Expand Up @@ -1051,6 +1052,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
self.binder = old_binder

def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None:
assert item.arguments is not None
for arg in item.arguments:
if arg.initializer is None:
continue
Expand Down
1 change: 1 addition & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3618,6 +3618,7 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla
# See https://github.com/python/mypy/issues/9927
return None, None

assert e.arguments is not None
arg_kinds = [arg.kind for arg in e.arguments]

if callable_ctx.is_ellipsis_args or ctx.param_spec() is not None:
Expand Down
6 changes: 4 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1950,10 +1950,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 is not None:
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
Expand Down
9 changes: 5 additions & 4 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,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 None if deserialized
'arg_names', # Names of arguments
'arg_kinds', # Kinds of arguments
'min_args', # Minimum number of arguments
Expand All @@ -658,14 +658,14 @@ 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: List[Argument],
body: 'Block',
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
super().__init__()
self.arguments = arguments
self.arguments: Optional[List[Argument]] = arguments
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in arguments]
self.arg_kinds: List[ArgKind] = [arg.kind for arg in self.arguments]
self.max_pos: int = (
Expand Down Expand Up @@ -693,6 +693,7 @@ def set_line(self,
column: Optional[int] = None,
end_line: Optional[int] = None) -> None:
super().set_line(target, column, end_line)
assert self.arguments is not None
for arg in self.arguments:
arg.set_line(self.line, self.column, self.end_line)

Expand Down Expand Up @@ -772,7 +773,7 @@ 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
ret.arguments = None
del ret.max_pos
del ret.min_args
return ret
Expand Down
2 changes: 2 additions & 0 deletions mypy/renaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def visit_func_def(self, fdef: FuncDef) -> None:
self.reject_redefinition_of_vars_in_scope()

with self.enter_scope(FUNCTION), self.enter_block():
assert fdef.arguments is not None
for arg in fdef.arguments:
name = arg.variable.name
# 'self' can't be redefined since it's special as it allows definition of
Expand Down Expand Up @@ -442,6 +443,7 @@ def visit_mypy_file(self, file_node: MypyFile) -> None:
def visit_func_def(self, fdef: FuncDef) -> None:
self.reject_redefinition_of_vars_in_scope()
with self.enter_scope():
assert fdef.arguments is not None
for arg in fdef.arguments:
self.record_skipped(arg.variable.name)
super().visit_func_def(fdef)
Expand Down
5 changes: 5 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ def visit_func_def(self, defn: FuncDef) -> None:
self.statement = defn

# Visit default values because they may contain assignment expressions.
assert defn.arguments is not None
for arg in defn.arguments:
if arg.initializer:
arg.initializer.accept(self)
Expand Down Expand Up @@ -980,6 +981,7 @@ def add_function_to_symbol_table(self, func: Union[FuncDef, OverloadedFuncDef])
def analyze_arg_initializers(self, defn: FuncItem) -> None:
with self.tvar_scope_frame(self.tvar_scope.method_frame()):
# Analyze default arguments
assert defn.arguments is not None
for arg in defn.arguments:
if arg.initializer:
arg.initializer.accept(self)
Expand All @@ -993,6 +995,7 @@ def analyze_function_body(self, defn: FuncItem) -> None:
a.bind_function_type_variables(cast(CallableType, defn.type), defn)
self.function_stack.append(defn)
with self.enter(defn):
assert defn.arguments is not None
for arg in defn.arguments:
self.add_local(arg.variable, defn)

Expand Down Expand Up @@ -1021,6 +1024,7 @@ def check_classvar_in_signature(self, typ: ProperType) -> None:
def check_function_signature(self, fdef: FuncItem) -> None:
sig = fdef.type
assert isinstance(sig, CallableType)
assert fdef.arguments is not None
if len(sig.arg_types) < len(fdef.arguments):
self.fail('Type signature has too few arguments', fdef)
# Add dummy Any arguments to prevent crashes later.
Expand Down Expand Up @@ -1073,6 +1077,7 @@ def visit_decorator(self, dec: Decorator) -> None:
elif refers_to_fullname(d, 'functools.cached_property'):
dec.var.is_settable_property = True
self.check_decorated_function_is_method('property', dec)
assert dec.func.arguments is not None
if len(dec.func.arguments) > 1:
self.fail('Too many arguments', dec.func)
elif refers_to_fullname(d, 'typing.no_type_check'):
Expand Down
2 changes: 2 additions & 0 deletions mypy/strconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def func_helper(self, o: 'mypy.nodes.FuncItem') -> List[object]:
"""
args: List[Union[mypy.nodes.Var, Tuple[str, List[mypy.nodes.Node]]]] = []
extra: List[Tuple[str, List[mypy.nodes.Var]]] = []
assert o.arguments is not None
for arg in o.arguments:
kind: mypy.nodes.ArgKind = arg.kind
if kind.is_required():
Expand Down Expand Up @@ -131,6 +132,7 @@ def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> str:
def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> str:
a = self.func_helper(o)
a.insert(0, o.name)
assert o.arguments is not None
arg_kinds = {arg.kind for arg in o.arguments}
if len(arg_kinds & {mypy.nodes.ARG_NAMED, mypy.nodes.ARG_NAMED_OPT}) > 0:
a.insert(1, 'MaxPos({})'.format(o.max_pos))
Expand Down
1 change: 1 addition & 0 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ def visit_func_def(self, o: FuncDef, is_abstract: bool = False,
self.add("%s%sdef %s(" % (self._indent, 'async ' if o.is_coroutine else '', o.name))
self.record_name(o.name)
args: List[str] = []
assert o.arguments is not None
for i, arg_ in enumerate(o.arguments):
var = arg_.variable
kind = arg_.kind
Expand Down
4 changes: 4 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ def get_desc(arg: Any) -> str:
@staticmethod
def from_funcitem(stub: nodes.FuncItem) -> "Signature[nodes.Argument]":
stub_sig: Signature[nodes.Argument] = Signature()
assert stub.arguments is not None
stub_args = maybe_strip_cls(stub.name, stub.arguments)
for stub_arg in stub_args:
if stub_arg.kind.is_positional():
Expand Down Expand Up @@ -544,6 +545,7 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> "Signature[nodes.Ar
all_args: Dict[str, List[Tuple[nodes.Argument, int]]] = {}
for func in map(_resolve_funcitem_from_decorator, stub.items):
assert func is not None
assert func.arguments is not None
args = maybe_strip_cls(stub.name, func.arguments)
for index, arg in enumerate(args):
# For positional-only args, we allow overloads to have different names for the same
Expand Down Expand Up @@ -916,9 +918,11 @@ def apply_decorator_to_funcitem(
):
return func
if decorator.fullname == "builtins.classmethod":
assert func.arguments is not None
assert func.arguments[0].variable.name in ("cls", "metacls")
ret = copy.copy(func)
# Remove the cls argument, since it's not present in inspect.signature of classmethods
assert ret.arguments
ret.arguments = ret.arguments[1:]
return ret
# Just give up on any other decorators. After excluding properties, we don't run into
Expand Down
4 changes: 4 additions & 0 deletions mypy/suggestions.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class ArgUseFinder(TraverserVisitor):
"""
def __init__(self, func: FuncDef, typemap: Dict[Expression, Type]) -> None:
self.typemap = typemap
assert func.arguments is not None
self.arg_types: Dict[SymbolNode, List[Type]] = {arg.variable: [] for arg in func.arguments}

def visit_call_expr(self, o: CallExpr) -> None:
Expand Down Expand Up @@ -179,6 +180,7 @@ def test(x, y):
"""
finder = ArgUseFinder(func, typemap)
func.body.accept(finder)
assert func.arguments is not None
return [finder.arg_types[arg.variable] for arg in func.arguments]


Expand Down Expand Up @@ -345,6 +347,7 @@ def get_args(self, is_method: bool,
return types

def get_default_arg_types(self, fdef: FuncDef) -> List[Optional[Type]]:
assert fdef.arguments is not None
return [
self.manager.all_types[arg.initializer] if arg.initializer else None
for arg in fdef.arguments
Expand Down Expand Up @@ -418,6 +421,7 @@ def get_guesses_from_parent(self, node: FuncDef) -> List[CallableType]:
if pnode and isinstance(pnode.node, (FuncDef, Decorator)):
typ = get_proper_type(pnode.node.type)
# FIXME: Doesn't work right with generic tyeps
assert node.arguments is not None
if isinstance(typ, CallableType) and len(typ.arg_types) == len(node.arguments):
# Return the first thing we find, since it probably doesn't make sense
# to grab things further up in the chain if an earlier parent has it.
Expand Down
3 changes: 3 additions & 0 deletions mypy/treetransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def visit_func_def(self, node: FuncDef) -> FuncDef:
for stmt in node.body.body:
stmt.accept(init)

assert node.arguments is not None
new = FuncDef(node.name,
[self.copy_argument(arg) for arg in node.arguments],
self.block(node.body),
Expand Down Expand Up @@ -139,6 +140,7 @@ def visit_func_def(self, node: FuncDef) -> FuncDef:
return new

def visit_lambda_expr(self, node: LambdaExpr) -> LambdaExpr:
assert node.arguments is not None
new = LambdaExpr([self.copy_argument(arg) for arg in node.arguments],
self.block(node.body),
cast(Optional[FunctionLike], self.optional_type(node.type)))
Expand Down Expand Up @@ -630,6 +632,7 @@ def __init__(self, transformer: TransformVisitor) -> None:
def visit_func_def(self, node: FuncDef) -> None:
if node not in self.transformer.func_placeholder_map:
# Haven't seen this FuncDef before, so create a placeholder node.
assert node.arguments is not None
self.transformer.func_placeholder_map[node] = FuncDef(
node.name, node.arguments, node.body, None)
super().visit_func_def(node)
17 changes: 7 additions & 10 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,16 +1262,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
Expand Down
1 change: 1 addition & 0 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ def gen_arg_defaults(builder: IRBuilder) -> None:
value to the argument.
"""
fitem = builder.fn_info.fitem
assert fitem.arguments is not None
for arg in fitem.arguments:
if arg.initializer:
target = builder.lookup(arg.variable)
Expand Down
1 change: 1 addition & 0 deletions mypyc/irbuild/env_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def add_args_to_env(builder: IRBuilder,
base: Optional[Union[FuncInfo, ImplicitClass]] = None,
reassign: bool = True) -> None:
fn_info = builder.fn_info
assert fn_info.fitem.arguments is not None
if local:
for arg in fn_info.fitem.arguments:
rtype = builder.type_to_rtype(arg.variable.type)
Expand Down
2 changes: 2 additions & 0 deletions mypyc/irbuild/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def transform_lambda_expr(builder: IRBuilder, expr: LambdaExpr) -> Value:
assert isinstance(typ, CallableType)

runtime_args = []
assert expr.arguments is not None
for arg, arg_type in zip(expr.arguments, typ.arg_types):
arg.variable.type = arg_type
runtime_args.append(
Expand Down Expand Up @@ -467,6 +468,7 @@ def calculate_arg_defaults(builder: IRBuilder,
still stored computed on demand).
"""
fitem = fn_info.fitem
assert fitem.arguments is not None
for arg in fitem.arguments:
# Constant values don't get stored but just recomputed
if arg.initializer and not is_constant(arg.initializer):
Expand Down
3 changes: 2 additions & 1 deletion mypyc/irbuild/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature:
ret = self.type_to_rtype(fdef.type.ret_type)
else:
# Handle unannotated functions
assert fdef.arguments is not None
arg_types = [object_rprimitive for arg in fdef.arguments]
arg_pos_onlys = [arg.pos_only for arg in fdef.arguments]
# We at least know the return type for __init__ methods will be None.
Expand All @@ -145,7 +146,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 is not None:
arg_names = [arg.variable.name for arg in fdef.arguments]
else:
arg_names = [name or '' for name in fdef.arg_names]
Expand Down
32 changes: 32 additions & 0 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -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