Check py3k keyword-only arguments

Take them into account when matching call sites versus function definitions. Closes #107788

authorJulien Cristau <julien.cristau@logilab.fr>
changeset4143143fcdfb
branchdefault
phasepublic
hiddenno
parent revision#2630f6a66747 add test for no-final-new-line
child revision#284f6e813fe4 ChangeLog update and cleanups
files modified by this revision
checkers/typecheck.py
test/input/func_kwoa_py30.py
test/messages/func_kwoa_py30.txt
# HG changeset patch
# User Julien Cristau <julien.cristau@logilab.fr>
# Date 1374181500 -7200
# Thu Jul 18 23:05:00 2013 +0200
# Node ID 4143143fcdfbba2519bb2fdcb7adcb397f730cd4
# Parent 2630f6a667472a4f8a4e0652759937b31ac4a5f1
Check py3k keyword-only arguments

Take them into account when matching call sites versus function
definitions. Closes #107788

diff --git a/checkers/typecheck.py b/checkers/typecheck.py
@@ -67,10 +67,15 @@
1      'E1124': ('Parameter %r passed as both positional and keyword argument',
2                'redundant-keyword-arg',
3                'Used when a function call would result in assigning multiple \
4                values to a function parameter, one value from a positional \
5                argument and one from a keyword argument.'),
6 +    'E1125': ('Missing mandatory keyword argument %r',
7 +              'missing-kwoa',
8 +              'Used when a function call doesn\'t pass a mandatory \
9 +              keyword-only argument.',
10 +              {'minversion': (3, 0)}),
11      }
12 
13  class TypeChecker(BaseChecker):
14      """try to find bugs in the code using type inference
15      """
@@ -314,10 +319,19 @@
16                  defval = called.args.defaults[i - num_mandatory_parameters]
17              else:
18                  defval = None
19              parameters.append([(name, defval), False])
20 
21 +        kwparams = {}
22 +        for i, arg in enumerate(called.args.kwonlyargs):
23 +            if isinstance(arg, astroid.Keyword):
24 +                name = arg.arg
25 +            else:
26 +                assert isinstance(arg, astroid.AssName)
27 +                name = arg.name
28 +            kwparams[name] = [called.args.kw_defaults[i], False]
29 +
30          # Match the supplied arguments against the function parameters.
31 
32          # 1. Match the positional arguments.
33          for i in range(num_positional_args):
34              if i < len(parameters):
@@ -338,10 +352,16 @@
35                  if parameters[i][1]:
36                      # Duplicate definition of function parameter.
37                      self.add_message('E1124', node=node, args=keyword)
38                  else:
39                      parameters[i][1] = True
40 +            elif keyword in kwparams:
41 +                if kwparams[keyword][1]:  # XXX is that even possible?
42 +                    # Duplicate definition of function parameter.
43 +                    self.add_message('E1124', node=node, args=keyword)
44 +                else:
45 +                    kwparams[keyword][1] = True
46              elif called.args.kwarg is not None:
47                  # The keyword argument gets assigned to the **kwargs parameter.
48                  pass
49              else:
50                  # Unexpected keyword argument.
@@ -382,8 +402,14 @@
51                      display_name = '<tuple>'
52                  else:
53                      display_name = repr(name)
54                  self.add_message('E1120', node=node, args=display_name)
55 
56 +        for name in kwparams:
57 +            defval, assigned = kwparams[name]
58 +            if defval is None and not assigned:
59 +                self.add_message('E1125', node=node, args=name)
60 +
61 +
62  def register(linter):
63      """required method to auto register this checker """
64      linter.register_checker(TypeChecker(linter))
diff --git a/test/input/func_kwoa_py30.py b/test/input/func_kwoa_py30.py
@@ -0,0 +1,12 @@
65 +# pylint: disable=C0121, C0102
66 +'''A little testscript for PEP 3102 and pylint'''
67 +def function(*, foo):
68 +    '''A function for testing'''
69 +    print(foo)
70 +
71 +function(foo=1)
72 +
73 +foo = 1
74 +function(foo)
75 +
76 +function(1)
diff --git a/test/messages/func_kwoa_py30.txt b/test/messages/func_kwoa_py30.txt
@@ -0,0 +1,5 @@
77 +E: 10: Missing mandatory keyword argument 'foo'
78 +E: 10: Too many positional arguments for function call
79 +E: 12: Missing mandatory keyword argument 'foo'
80 +E: 12: Too many positional arguments for function call
81 +W:  3:function: Redefining name 'foo' from outer scope (line 9)