Skip to content

Commit 4f6c730

Browse files
committed
Make FuncItem.arguments optional
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 None, and mypy can remind us to check for not None before use.
1 parent 7c04e7c commit 4f6c730

16 files changed

+46
-17
lines changed

mypy/checker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
10031003
arg_type = self.named_generic_type('builtins.dict',
10041004
[self.str_type(),
10051005
arg_type])
1006+
assert item.arguments is not None
10061007
item.arguments[i].variable.type = arg_type
10071008

10081009
# Type check initialization expressions.
@@ -1049,6 +1050,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
10491050
self.binder = old_binder
10501051

10511052
def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None:
1053+
assert item.arguments is not None
10521054
for arg in item.arguments:
10531055
if arg.initializer is None:
10541056
continue

mypy/checkexpr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3586,6 +3586,7 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla
35863586
# See https://github.com/python/mypy/issues/9927
35873587
return None, None
35883588

3589+
assert e.arguments is not None
35893590
arg_kinds = [arg.kind for arg in e.arguments]
35903591

35913592
if callable_ctx.is_ellipsis_args or ctx.param_spec() is not None:

mypy/messages.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,10 +1975,12 @@ def [T <: int] f(self, x: int, y: T) -> None
19751975
s += ' = ...'
19761976

19771977
# If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list
1978-
if isinstance(tp.definition, FuncDef) and tp.definition.name is not None:
1978+
if isinstance(tp.definition, FuncDef) and \
1979+
tp.definition.name is not None and \
1980+
tp.definition.arguments is not None:
19791981
definition_args = [arg.variable.name for arg in tp.definition.arguments]
19801982
if definition_args and tp.arg_names != definition_args \
1981-
and len(definition_args) > 0 and definition_args[0]:
1983+
and len(definition_args) > 0 and definition_args[0]:
19821984
if s:
19831985
s = ', ' + s
19841986
s = definition_args[0] + s

mypy/nodes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ def set_line(self,
649649
class FuncItem(FuncBase):
650650
"""Base class for nodes usable as overloaded function items."""
651651

652-
__slots__ = ('arguments', # Note that can be None if deserialized (type is a lie!)
652+
__slots__ = ('arguments', # Note that can be None if deserialized
653653
'arg_names', # Names of arguments
654654
'arg_kinds', # Kinds of arguments
655655
'min_args', # Minimum number of arguments
@@ -665,14 +665,14 @@ class FuncItem(FuncBase):
665665
'expanded', # Variants of function with type variables with values expanded
666666
)
667667

668-
__deletable__ = ('arguments', 'max_pos', 'min_args')
668+
__deletable__ = ('max_pos', 'min_args')
669669

670670
def __init__(self,
671671
arguments: List[Argument],
672672
body: 'Block',
673673
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
674674
super().__init__()
675-
self.arguments = arguments
675+
self.arguments: Optional[List[Argument]] = arguments
676676
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in arguments]
677677
self.arg_kinds: List[ArgKind] = [arg.kind for arg in self.arguments]
678678
self.max_pos: int = (
@@ -700,6 +700,7 @@ def set_line(self,
700700
column: Optional[int] = None,
701701
end_line: Optional[int] = None) -> None:
702702
super().set_line(target, column, end_line)
703+
assert self.arguments is not None
703704
for arg in self.arguments:
704705
arg.set_line(self.line, self.column, self.end_line)
705706

@@ -779,7 +780,7 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef':
779780
ret.arg_names = data['arg_names']
780781
ret.arg_kinds = [ArgKind(x) for x in data['arg_kinds']]
781782
# Leave these uninitialized so that future uses will trigger an error
782-
del ret.arguments
783+
ret.arguments = None
783784
del ret.max_pos
784785
del ret.min_args
785786
return ret

mypy/renaming.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def visit_func_def(self, fdef: FuncDef) -> None:
8686
self.reject_redefinition_of_vars_in_scope()
8787

8888
with self.enter_scope(FUNCTION), self.enter_block():
89+
assert fdef.arguments is not None
8990
for arg in fdef.arguments:
9091
name = arg.variable.name
9192
# 'self' can't be redefined since it's special as it allows definition of
@@ -442,6 +443,7 @@ def visit_mypy_file(self, file_node: MypyFile) -> None:
442443
def visit_func_def(self, fdef: FuncDef) -> None:
443444
self.reject_redefinition_of_vars_in_scope()
444445
with self.enter_scope():
446+
assert fdef.arguments is not None
445447
for arg in fdef.arguments:
446448
self.record_skipped(arg.variable.name)
447449
super().visit_func_def(fdef)

mypy/semanal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ def visit_func_def(self, defn: FuncDef) -> None:
612612
self.statement = defn
613613

614614
# Visit default values because they may contain assignment expressions.
615+
assert defn.arguments is not None
615616
for arg in defn.arguments:
616617
if arg.initializer:
617618
arg.initializer.accept(self)
@@ -985,6 +986,7 @@ def add_function_to_symbol_table(self, func: Union[FuncDef, OverloadedFuncDef])
985986
def analyze_arg_initializers(self, defn: FuncItem) -> None:
986987
with self.tvar_scope_frame(self.tvar_scope.method_frame()):
987988
# Analyze default arguments
989+
assert defn.arguments is not None
988990
for arg in defn.arguments:
989991
if arg.initializer:
990992
arg.initializer.accept(self)
@@ -998,6 +1000,7 @@ def analyze_function_body(self, defn: FuncItem) -> None:
9981000
a.bind_function_type_variables(cast(CallableType, defn.type), defn)
9991001
self.function_stack.append(defn)
10001002
with self.enter(defn):
1003+
assert defn.arguments is not None
10011004
for arg in defn.arguments:
10021005
self.add_local(arg.variable, defn)
10031006

@@ -1026,6 +1029,7 @@ def check_classvar_in_signature(self, typ: ProperType) -> None:
10261029
def check_function_signature(self, fdef: FuncItem) -> None:
10271030
sig = fdef.type
10281031
assert isinstance(sig, CallableType)
1032+
assert fdef.arguments is not None
10291033
if len(sig.arg_types) < len(fdef.arguments):
10301034
self.fail('Type signature has too few arguments', fdef)
10311035
# Add dummy Any arguments to prevent crashes later.
@@ -1078,6 +1082,7 @@ def visit_decorator(self, dec: Decorator) -> None:
10781082
elif refers_to_fullname(d, 'functools.cached_property'):
10791083
dec.var.is_settable_property = True
10801084
self.check_decorated_function_is_method('property', dec)
1085+
assert dec.func.arguments is not None
10811086
if len(dec.func.arguments) > 1:
10821087
self.fail('Too many arguments', dec.func)
10831088
elif refers_to_fullname(d, 'typing.no_type_check'):

mypy/strconv.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def func_helper(self, o: 'mypy.nodes.FuncItem') -> List[object]:
6464
"""
6565
args: List[Union[mypy.nodes.Var, Tuple[str, List[mypy.nodes.Node]]]] = []
6666
extra: List[Tuple[str, List[mypy.nodes.Var]]] = []
67+
assert o.arguments is not None
6768
for arg in o.arguments:
6869
kind: mypy.nodes.ArgKind = arg.kind
6970
if kind.is_required():
@@ -131,6 +132,7 @@ def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> str:
131132
def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> str:
132133
a = self.func_helper(o)
133134
a.insert(0, o.name)
135+
assert o.arguments is not None
134136
arg_kinds = {arg.kind for arg in o.arguments}
135137
if len(arg_kinds & {mypy.nodes.ARG_NAMED, mypy.nodes.ARG_NAMED_OPT}) > 0:
136138
a.insert(1, f'MaxPos({o.max_pos})')

mypy/stubgen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,7 @@ def visit_func_def(self, o: FuncDef, is_abstract: bool = False,
638638
self.add(f"{self._indent}{'async ' if o.is_coroutine else ''}def {o.name}(")
639639
self.record_name(o.name)
640640
args: List[str] = []
641+
assert o.arguments is not None
641642
for i, arg_ in enumerate(o.arguments):
642643
var = arg_.variable
643644
kind = arg_.kind

mypy/stubtest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ def get_desc(arg: Any) -> str:
496496
@staticmethod
497497
def from_funcitem(stub: nodes.FuncItem) -> "Signature[nodes.Argument]":
498498
stub_sig: Signature[nodes.Argument] = Signature()
499+
assert stub.arguments is not None
499500
stub_args = maybe_strip_cls(stub.name, stub.arguments)
500501
for stub_arg in stub_args:
501502
if stub_arg.kind.is_positional():
@@ -545,6 +546,7 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> "Signature[nodes.Ar
545546
all_args: Dict[str, List[Tuple[nodes.Argument, int]]] = {}
546547
for func in map(_resolve_funcitem_from_decorator, stub.items):
547548
assert func is not None
549+
assert func.arguments is not None
548550
args = maybe_strip_cls(stub.name, func.arguments)
549551
for index, arg in enumerate(args):
550552
# For positional-only args, we allow overloads to have different names for the same
@@ -916,9 +918,11 @@ def apply_decorator_to_funcitem(
916918
) or decorator.fullname in mypy.types.OVERLOAD_NAMES:
917919
return func
918920
if decorator.fullname == "builtins.classmethod":
921+
assert func.arguments is not None
919922
assert func.arguments[0].variable.name in ("cls", "metacls")
920923
ret = copy.copy(func)
921924
# Remove the cls argument, since it's not present in inspect.signature of classmethods
925+
assert ret.arguments
922926
ret.arguments = ret.arguments[1:]
923927
return ret
924928
# Just give up on any other decorators. After excluding properties, we don't run into

mypy/suggestions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class ArgUseFinder(TraverserVisitor):
144144
"""
145145
def __init__(self, func: FuncDef, typemap: Dict[Expression, Type]) -> None:
146146
self.typemap = typemap
147+
assert func.arguments is not None
147148
self.arg_types: Dict[SymbolNode, List[Type]] = {arg.variable: [] for arg in func.arguments}
148149

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

184186

@@ -345,6 +347,7 @@ def get_args(self, is_method: bool,
345347
return types
346348

347349
def get_default_arg_types(self, fdef: FuncDef) -> List[Optional[Type]]:
350+
assert fdef.arguments is not None
348351
return [
349352
self.manager.all_types[arg.initializer] if arg.initializer else None
350353
for arg in fdef.arguments
@@ -418,6 +421,7 @@ def get_guesses_from_parent(self, node: FuncDef) -> List[CallableType]:
418421
if pnode and isinstance(pnode.node, (FuncDef, Decorator)):
419422
typ = get_proper_type(pnode.node.type)
420423
# FIXME: Doesn't work right with generic tyeps
424+
assert node.arguments is not None
421425
if isinstance(typ, CallableType) and len(typ.arg_types) == len(node.arguments):
422426
# Return the first thing we find, since it probably doesn't make sense
423427
# to grab things further up in the chain if an earlier parent has it.

mypy/treetransform.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def visit_func_def(self, node: FuncDef) -> FuncDef:
110110
for stmt in node.body.body:
111111
stmt.accept(init)
112112

113+
assert node.arguments is not None
113114
new = FuncDef(node.name,
114115
[self.copy_argument(arg) for arg in node.arguments],
115116
self.block(node.body),
@@ -139,6 +140,7 @@ def visit_func_def(self, node: FuncDef) -> FuncDef:
139140
return new
140141

141142
def visit_lambda_expr(self, node: LambdaExpr) -> LambdaExpr:
143+
assert node.arguments is not None
142144
new = LambdaExpr([self.copy_argument(arg) for arg in node.arguments],
143145
self.block(node.body),
144146
cast(Optional[FunctionLike], self.optional_type(node.type)))
@@ -638,6 +640,7 @@ def __init__(self, transformer: TransformVisitor) -> None:
638640
def visit_func_def(self, node: FuncDef) -> None:
639641
if node not in self.transformer.func_placeholder_map:
640642
# Haven't seen this FuncDef before, so create a placeholder node.
643+
assert node.arguments is not None
641644
self.transformer.func_placeholder_map[node] = FuncDef(
642645
node.name, node.arguments, node.body, None)
643646
super().visit_func_def(node)

mypy/types.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,16 +1525,13 @@ def __init__(self,
15251525
# after serialization, but it is useful in error messages.
15261526
# TODO: decide how to add more info here (file, line, column)
15271527
# without changing interface hash.
1528-
self.def_extras = {
1529-
'first_arg': (
1530-
definition.arguments[0].variable.name
1531-
if (getattr(definition, 'arguments', None)
1532-
and definition.arg_names
1533-
and definition.info
1534-
and not definition.is_static)
1535-
else None
1536-
),
1537-
}
1528+
first_arg: Optional[str] = None
1529+
if definition.info and not definition.is_static:
1530+
if definition.arguments:
1531+
first_arg = definition.arguments[0].variable.name
1532+
elif definition.arg_names:
1533+
first_arg = definition.arg_names[0]
1534+
self.def_extras = {'first_arg': first_arg}
15381535
else:
15391536
self.def_extras = {}
15401537
self.type_guard = type_guard

mypyc/irbuild/builder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,7 @@ def gen_arg_defaults(builder: IRBuilder) -> None:
11841184
value to the argument.
11851185
"""
11861186
fitem = builder.fn_info.fitem
1187+
assert fitem.arguments is not None
11871188
for arg in fitem.arguments:
11881189
if arg.initializer:
11891190
target = builder.lookup(arg.variable)

mypyc/irbuild/env_class.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ def add_args_to_env(builder: IRBuilder,
160160
base: Optional[Union[FuncInfo, ImplicitClass]] = None,
161161
reassign: bool = True) -> None:
162162
fn_info = builder.fn_info
163+
assert fn_info.fitem.arguments is not None
163164
if local:
164165
for arg in fn_info.fitem.arguments:
165166
rtype = builder.type_to_rtype(arg.variable.type)

mypyc/irbuild/function.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def transform_lambda_expr(builder: IRBuilder, expr: LambdaExpr) -> Value:
122122
assert isinstance(typ, CallableType)
123123

124124
runtime_args = []
125+
assert expr.arguments is not None
125126
for arg, arg_type in zip(expr.arguments, typ.arg_types):
126127
arg.variable.type = arg_type
127128
runtime_args.append(
@@ -467,6 +468,7 @@ def calculate_arg_defaults(builder: IRBuilder,
467468
still stored computed on demand).
468469
"""
469470
fitem = fn_info.fitem
471+
assert fitem.arguments is not None
470472
for arg in fitem.arguments:
471473
# Constant values don't get stored but just recomputed
472474
if arg.initializer and not is_constant(arg.initializer):

mypyc/irbuild/mapper.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature:
128128
ret = self.type_to_rtype(fdef.type.ret_type)
129129
else:
130130
# Handle unannotated functions
131+
assert fdef.arguments is not None
131132
arg_types = [object_rprimitive for arg in fdef.arguments]
132133
arg_pos_onlys = [arg.pos_only for arg in fdef.arguments]
133134
# We at least know the return type for __init__ methods will be None.
@@ -145,7 +146,7 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature:
145146
# deserialized FuncDef that lacks arguments. We won't ever
146147
# need to use those inside of a FuncIR, so we just make up
147148
# some crap.
148-
if hasattr(fdef, 'arguments'):
149+
if fdef.arguments is not None:
149150
arg_names = [arg.variable.name for arg in fdef.arguments]
150151
else:
151152
arg_names = [name or '' for name in fdef.arg_names]

0 commit comments

Comments
 (0)