Skip to content

Commit d180456

Browse files
authored
Properly track positional-only arguments for unannotated functions (#10802)
I was originally planning to address this by adding an ARG_POS_ONLY kind (as a concrete win for what I hoped would be a generally positive refactor), but it was actually really easy to fix it on its own, and then we can address the refactor purely on its own merits.
1 parent ed09f8d commit d180456

File tree

9 files changed

+73
-32
lines changed

9 files changed

+73
-32
lines changed

mypy/fastparse.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -522,14 +522,13 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
522522

523523
lineno = n.lineno
524524
args = self.transform_args(n.args, lineno, no_type_check=no_type_check)
525+
if special_function_elide_names(n.name):
526+
for arg in args:
527+
arg.pos_only = True
525528

526-
posonlyargs = [arg.arg for arg in getattr(n.args, "posonlyargs", [])]
527529
arg_kinds = [arg.kind for arg in args]
528-
arg_names: List[Optional[str]] = [arg.variable.name for arg in args]
529-
arg_names = [None if argument_elide_name(name) or name in posonlyargs else name
530-
for name in arg_names]
531-
if special_function_elide_names(n.name):
532-
arg_names = [None] * len(arg_names)
530+
arg_names = [None if arg.pos_only else arg.variable.name for arg in args]
531+
533532
arg_types: List[Optional[Type]] = []
534533
if no_type_check:
535534
arg_types = [None] * len(args)
@@ -602,10 +601,11 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
602601
AnyType(TypeOfAny.unannotated),
603602
_dummy_fallback)
604603

605-
func_def = FuncDef(n.name,
606-
args,
607-
self.as_required_block(n.body, lineno),
608-
func_type)
604+
func_def = FuncDef(
605+
n.name,
606+
args,
607+
self.as_required_block(n.body, lineno),
608+
func_type)
609609
if isinstance(func_def.type, CallableType):
610610
# semanal.py does some in-place modifications we want to avoid
611611
func_def.unanalyzed_type = func_def.type.copy_modified()
@@ -660,17 +660,20 @@ def transform_args(self,
660660
) -> List[Argument]:
661661
new_args = []
662662
names: List[ast3.arg] = []
663-
args_args = getattr(args, "posonlyargs", cast(List[ast3.arg], [])) + args.args
663+
posonlyargs = getattr(args, "posonlyargs", cast(List[ast3.arg], []))
664+
args_args = posonlyargs + args.args
664665
args_defaults = args.defaults
665666
num_no_defaults = len(args_args) - len(args_defaults)
666667
# positional arguments without defaults
667-
for a in args_args[:num_no_defaults]:
668-
new_args.append(self.make_argument(a, None, ARG_POS, no_type_check))
668+
for i, a in enumerate(args_args[:num_no_defaults]):
669+
pos_only = i < len(posonlyargs)
670+
new_args.append(self.make_argument(a, None, ARG_POS, no_type_check, pos_only))
669671
names.append(a)
670672

671673
# positional arguments with defaults
672-
for a, d in zip(args_args[num_no_defaults:], args_defaults):
673-
new_args.append(self.make_argument(a, d, ARG_OPT, no_type_check))
674+
for i, (a, d) in enumerate(zip(args_args[num_no_defaults:], args_defaults)):
675+
pos_only = num_no_defaults + i < len(posonlyargs)
676+
new_args.append(self.make_argument(a, d, ARG_OPT, no_type_check, pos_only))
674677
names.append(a)
675678

676679
# *arg
@@ -697,7 +700,7 @@ def transform_args(self,
697700
return new_args
698701

699702
def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: ArgKind,
700-
no_type_check: bool) -> Argument:
703+
no_type_check: bool, pos_only: bool = False) -> Argument:
701704
if no_type_check:
702705
arg_type = None
703706
else:
@@ -710,7 +713,10 @@ def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: ArgKi
710713
arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation)
711714
else:
712715
arg_type = self.translate_type_comment(arg, type_comment)
713-
return Argument(Var(arg.arg), arg_type, self.visit(default), kind)
716+
if argument_elide_name(arg.arg):
717+
pos_only = True
718+
719+
return Argument(Var(arg.arg), arg_type, self.visit(default), kind, pos_only)
714720

715721
def fail_arg(self, msg: str, arg: ast3.arg) -> None:
716722
self.fail(msg, arg.lineno, arg.col_offset)

mypy/fastparse2.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,12 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:
369369
converter = TypeConverter(self.errors, line=lineno, override_column=n.col_offset,
370370
assume_str_is_unicode=self.unicode_literals)
371371
args, decompose_stmts = self.transform_args(n.args, lineno)
372+
if special_function_elide_names(n.name):
373+
for arg in args:
374+
arg.pos_only = True
372375

373376
arg_kinds = [arg.kind for arg in args]
374-
arg_names: List[Optional[str]] = [arg.variable.name for arg in args]
375-
arg_names = [None if argument_elide_name(name) else name for name in arg_names]
376-
if special_function_elide_names(n.name):
377-
arg_names = [None] * len(arg_names)
377+
arg_names = [None if arg.pos_only else arg.variable.name for arg in args]
378378

379379
arg_types: List[Optional[Type]] = []
380380
type_comment = n.type_comment
@@ -518,6 +518,10 @@ def transform_args(self,
518518
new_args.append(Argument(Var(n.kwarg), typ, None, ARG_STAR2))
519519
names.append(n.kwarg)
520520

521+
for arg in new_args:
522+
if argument_elide_name(arg.variable.name):
523+
arg.pos_only = True
524+
521525
# We don't have any context object to give, but we have closed around the line num
522526
def fail_arg(msg: str, arg: None) -> None:
523527
self.fail(msg, line, 0)

mypy/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,9 +1939,9 @@ def [T <: int] f(self, x: int, y: T) -> None
19391939

19401940
# If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list
19411941
if isinstance(tp.definition, FuncDef) and tp.definition.name is not None:
1942-
definition_args = tp.definition.arg_names
1942+
definition_args = [arg.variable.name for arg in tp.definition.arguments]
19431943
if definition_args and tp.arg_names != definition_args \
1944-
and len(definition_args) > 0:
1944+
and len(definition_args) > 0 and definition_args[0]:
19451945
if s:
19461946
s = ', ' + s
19471947
s = definition_args[0] + s

mypy/nodes.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,18 +561,20 @@ def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef':
561561
class Argument(Node):
562562
"""A single argument in a FuncItem."""
563563

564-
__slots__ = ('variable', 'type_annotation', 'initializer', 'kind')
564+
__slots__ = ('variable', 'type_annotation', 'initializer', 'kind', 'pos_only')
565565

566566
def __init__(self,
567567
variable: 'Var',
568568
type_annotation: 'Optional[mypy.types.Type]',
569569
initializer: Optional[Expression],
570-
kind: 'ArgKind') -> None:
570+
kind: 'ArgKind',
571+
pos_only: bool = False) -> None:
571572
super().__init__()
572573
self.variable = variable
573574
self.type_annotation = type_annotation
574575
self.initializer = initializer
575576
self.kind = kind # must be an ARG_* constant
577+
self.pos_only = pos_only
576578

577579
def set_line(self,
578580
target: Union[Context, int],
@@ -619,7 +621,7 @@ def __init__(self,
619621
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
620622
super().__init__()
621623
self.arguments = arguments
622-
self.arg_names = [arg.variable.name for arg in self.arguments]
624+
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in arguments]
623625
self.arg_kinds: List[ArgKind] = [arg.kind for arg in self.arguments]
624626
self.max_pos: int = (
625627
self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT))

mypy/typeops.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
)
2323
from mypy.maptype import map_instance_to_supertype
2424
from mypy.expandtype import expand_type_by_instance, expand_type
25-
from mypy.sharedparse import argument_elide_name
2625

2726
from mypy.typevars import fill_typevars
2827

@@ -564,7 +563,7 @@ def callable_type(fdef: FuncItem, fallback: Instance,
564563
return CallableType(
565564
args,
566565
fdef.arg_kinds,
567-
[None if argument_elide_name(n) else n for n in fdef.arg_names],
566+
fdef.arg_names,
568567
ret_type or AnyType(TypeOfAny.unannotated),
569568
fallback,
570569
name=fdef.name,

mypy/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ def __init__(self,
10771077
# after serialization, but it is useful in error messages.
10781078
# TODO: decide how to add more info here (file, line, column)
10791079
# without changing interface hash.
1080-
self.def_extras = {'first_arg': definition.arg_names[0]
1080+
self.def_extras = {'first_arg': definition.arguments[0].variable.name
10811081
if definition.arg_names and definition.info and
10821082
not definition.is_static else None}
10831083
else:

mypyc/irbuild/mapper.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,21 @@ def fdef_to_sig(self, fdef: FuncDef) -> FuncSignature:
133133
else:
134134
ret = object_rprimitive
135135

136+
# mypyc FuncSignatures (unlike mypy types) want to have a name
137+
# present even when the argument is position only, since it is
138+
# the sole way that FuncDecl arguments are tracked. This is
139+
# generally fine except in some cases (like for computing
140+
# init_sig) we need to produce FuncSignatures from a
141+
# deserialized FuncDef that lacks arguments. We won't ever
142+
# need to use those inside of a FuncIR, so we just make up
143+
# some crap.
144+
if hasattr(fdef, 'arguments'):
145+
arg_names = [arg.variable.name for arg in fdef.arguments]
146+
else:
147+
arg_names = [name or '' for name in fdef.arg_names]
148+
136149
args = [RuntimeArg(arg_name, arg_type, arg_kind)
137-
for arg_name, arg_kind, arg_type in zip(fdef.arg_names, fdef.arg_kinds, arg_types)]
150+
for arg_name, arg_kind, arg_type in zip(arg_names, fdef.arg_kinds, arg_types)]
138151

139152
# We force certain dunder methods to return objects to support letting them
140153
# return NotImplemented. It also avoids some pointless boxing and unboxing,

test-data/unit/check-classes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2759,7 +2759,7 @@ t = Test()
27592759
t.crash = 'test' # E: "Test" has no attribute "crash"
27602760

27612761
class A:
2762-
def __setattr__(self): ... # E: Invalid signature "def (self: __main__.A) -> Any" for "__setattr__"
2762+
def __setattr__(self): ... # E: Invalid signature "def (__main__.A) -> Any" for "__setattr__"
27632763
a = A()
27642764
a.test = 4 # E: "A" has no attribute "test"
27652765

test-data/unit/check-python38.test

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,23 @@ def f(p1: bytes, p2: float, /) -> None:
192192
reveal_type(p1) # N: Revealed type is "builtins.bytes"
193193
reveal_type(p2) # N: Revealed type is "builtins.float"
194194

195+
[case testPEP570Unannotated]
196+
def f(arg, /): ...
197+
g = lambda arg, /: arg
198+
def h(arg=0, /): ...
199+
i = lambda arg=0, /: arg
200+
201+
f(1)
202+
g(1)
203+
h()
204+
h(1)
205+
i()
206+
i(1)
207+
f(arg=0) # E: Unexpected keyword argument "arg" for "f"
208+
g(arg=0) # E: Unexpected keyword argument "arg"
209+
h(arg=0) # E: Unexpected keyword argument "arg" for "h"
210+
i(arg=0) # E: Unexpected keyword argument "arg"
211+
195212
[case testWalrus]
196213
# flags: --strict-optional
197214
from typing import NamedTuple, Optional, List
@@ -206,7 +223,7 @@ while b := "x":
206223
l = [y2 := 1, y2 + 2, y2 + 3]
207224
reveal_type(y2) # N: Revealed type is "builtins.int"
208225
reveal_type(l) # N: Revealed type is "builtins.list[builtins.int*]"
209-
226+
210227
filtered_data = [y3 for x in l if (y3 := a) is not None]
211228
reveal_type(filtered_data) # N: Revealed type is "builtins.list[builtins.int*]"
212229
reveal_type(y3) # N: Revealed type is "builtins.int"

0 commit comments

Comments
 (0)