Skip to content

Commit ae8b284

Browse files
committed
Properly track positional-only arguments for unannotated functions
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 612d59a commit ae8b284

File tree

4 files changed

+25
-10
lines changed

4 files changed

+25
-10
lines changed

mypy/fastparse.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -602,10 +602,12 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
602602
AnyType(TypeOfAny.unannotated),
603603
_dummy_fallback)
604604

605-
func_def = FuncDef(n.name,
606-
args,
607-
self.as_required_block(n.body, lineno),
608-
func_type)
605+
func_def = FuncDef(
606+
n.name,
607+
args,
608+
self.as_required_block(n.body, lineno),
609+
func_type,
610+
num_pos_only=len(posonlyargs))
609611
if isinstance(func_def.type, CallableType):
610612
# semanal.py does some in-place modifications we want to avoid
611613
func_def.unanalyzed_type = func_def.type.copy_modified()
@@ -992,8 +994,10 @@ def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr:
992994
body.lineno = n.body.lineno
993995
body.col_offset = n.body.col_offset
994996

997+
num_pos_only = len(getattr(n.args, "posonlyargs", []))
995998
e = LambdaExpr(self.transform_args(n.args, n.lineno),
996-
self.as_required_block([body], n.lineno))
999+
self.as_required_block([body], n.lineno),
1000+
num_pos_only=num_pos_only)
9971001
e.set_line(n.lineno, n.col_offset) # Overrides set_line -- can't use self.set_line
9981002
return e
9991003

mypy/nodes.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ class FuncItem(FuncBase):
598598
__slots__ = ('arguments', # Note that can be None if deserialized (type is a lie!)
599599
'arg_names', # Names of arguments
600600
'arg_kinds', # Kinds of arguments
601+
'num_pos_only', # Number of positional only args
601602
'min_args', # Minimum number of arguments
602603
'max_pos', # Maximum number of positional arguments, -1 if no explicit
603604
# limit (*args not included)
@@ -616,11 +617,13 @@ class FuncItem(FuncBase):
616617
def __init__(self,
617618
arguments: List[Argument],
618619
body: 'Block',
619-
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
620+
typ: 'Optional[mypy.types.FunctionLike]' = None,
621+
num_pos_only: int = 0) -> None:
620622
super().__init__()
621623
self.arguments = arguments
622624
self.arg_names = [arg.variable.name for arg in self.arguments]
623625
self.arg_kinds: List[ArgKind] = [arg.kind for arg in self.arguments]
626+
self.num_pos_only = num_pos_only
624627
self.max_pos: int = (
625628
self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT))
626629
self.body: 'Block' = body
@@ -675,8 +678,9 @@ def __init__(self,
675678
name: str, # Function name
676679
arguments: List[Argument],
677680
body: 'Block',
678-
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
679-
super().__init__(arguments, body, typ)
681+
typ: 'Optional[mypy.types.FunctionLike]' = None,
682+
num_pos_only: int = 0) -> None:
683+
super().__init__(arguments, body, typ, num_pos_only)
680684
self._name = name
681685
self.is_decorated = False
682686
self.is_conditional = False # Defined conditionally (within block)?

mypy/typeops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ def callable_type(fdef: FuncItem, fallback: Instance,
564564
return CallableType(
565565
args,
566566
fdef.arg_kinds,
567-
[None if argument_elide_name(n) else n for n in fdef.arg_names],
567+
[None if argument_elide_name(n) or i < fdef.num_pos_only else n
568+
for i, n in enumerate(fdef.arg_names)],
568569
ret_type or AnyType(TypeOfAny.unannotated),
569570
fallback,
570571
name=fdef.name,

test-data/unit/check-python38.test

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ 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+
f(arg=0) # E: Unexpected keyword argument "arg" for "f"
199+
g(arg=0) # E: Unexpected keyword argument "arg"
200+
195201
[case testWalrus]
196202
# flags: --strict-optional
197203
from typing import NamedTuple, Optional, List
@@ -206,7 +212,7 @@ while b := "x":
206212
l = [y2 := 1, y2 + 2, y2 + 3]
207213
reveal_type(y2) # N: Revealed type is "builtins.int"
208214
reveal_type(l) # N: Revealed type is "builtins.list[builtins.int*]"
209-
215+
210216
filtered_data = [y3 for x in l if (y3 := a) is not None]
211217
reveal_type(filtered_data) # N: Revealed type is "builtins.list[builtins.int*]"
212218
reveal_type(y3) # N: Revealed type is "builtins.int"

0 commit comments

Comments
 (0)