1.1 --- a/deducer.py Wed Feb 08 01:21:39 2017 +0100
1.2 +++ b/deducer.py Wed Feb 08 16:20:27 2017 +0100
1.3 @@ -1863,8 +1863,8 @@
1.4 'object_type' identified by 'ref' is compatible with any arguments used.
1.5 """
1.6
1.7 - arguments = self.reference_invocations.get(location)
1.8 - if arguments is None:
1.9 + invocation = self.reference_invocations.get(location)
1.10 + if invocation is None:
1.11 return True
1.12
1.13 objpath = ref.get_origin()
1.14 @@ -1876,12 +1876,15 @@
1.15 return True
1.16
1.17 defaults = self.importer.function_defaults.get(objpath)
1.18 + arguments, keywords = invocation
1.19 + names = set(parameters)
1.20
1.21 # Determine whether the specified arguments are
1.22 # compatible with the callable signature.
1.23
1.24 if arguments >= len(parameters) - len(defaults) and \
1.25 - arguments <= len(parameters):
1.26 + arguments <= len(parameters) and \
1.27 + names.issuperset(keywords):
1.28
1.29 return True
1.30 else:
2.1 --- a/encoders.py Wed Feb 08 01:21:39 2017 +0100
2.2 +++ b/encoders.py Wed Feb 08 16:20:27 2017 +0100
2.3 @@ -93,7 +93,8 @@
2.4 if assignment:
2.5 return "="
2.6 elif invocation is not None:
2.7 - return "(%d)" % invocation
2.8 + arguments, keywords = invocation
2.9 + return "(%d;%s)" % (arguments, ",".join(keywords))
2.10 else:
2.11 return "_"
2.12
2.13 @@ -111,8 +112,13 @@
2.14 modifiers.append((True, None))
2.15 i += 1
2.16 elif s[i] == "(":
2.17 + j = s.index(";", i)
2.18 + arguments = int(s[i+1:j])
2.19 + i = j
2.20 j = s.index(")", i)
2.21 - modifiers.append((False, int(s[i+1:j])))
2.22 + keywords = s[i+1:j]
2.23 + keywords = keywords and keywords.split(",") or []
2.24 + modifiers.append((False, (arguments, keywords)))
2.25 i = j + 1
2.26 else:
2.27 modifiers.append((False, None))
3.1 --- a/inspector.py Wed Feb 08 01:21:39 2017 +0100
3.2 +++ b/inspector.py Wed Feb 08 16:20:27 2017 +0100
3.3 @@ -438,7 +438,8 @@
3.4 # Record attribute usage in the tracker, and record the branch
3.5 # information for the access.
3.6
3.7 - branches = tracker.use_attribute(name, attrname, self.in_invocation is not None, assignment)
3.8 + branches = tracker.use_attribute(name, attrname,
3.9 + self.in_invocation is not None, assignment)
3.10
3.11 if not branches:
3.12 raise InspectError("Name %s is accessed using %s before an assignment." % (
3.13 @@ -742,22 +743,29 @@
3.14 self.allocate_arguments(path, n.args)
3.15
3.16 try:
3.17 + in_invocation = self.in_invocation
3.18 + self.in_invocation = None
3.19 +
3.20 + # Process the arguments.
3.21 +
3.22 + keywords = set()
3.23 +
3.24 + for arg in n.args:
3.25 + self.process_structure_node(arg)
3.26 + if isinstance(arg, compiler.ast.Keyword):
3.27 + keywords.add(arg.name)
3.28 +
3.29 + keywords = list(keywords)
3.30 + keywords.sort()
3.31 +
3.32 # Communicate to the invocation target expression that it forms the
3.33 # target of an invocation, potentially affecting attribute accesses.
3.34
3.35 - in_invocation = self.in_invocation
3.36 - self.in_invocation = len(n.args)
3.37 + self.in_invocation = len(n.args), keywords
3.38
3.39 # Process the expression, obtaining any identified reference.
3.40
3.41 name_ref = self.process_structure_node(n.node)
3.42 - self.in_invocation = None
3.43 -
3.44 - # Process the arguments.
3.45 -
3.46 - for arg in n.args:
3.47 - self.process_structure_node(arg)
3.48 -
3.49 self.in_invocation = in_invocation
3.50
3.51 # Detect class invocations.
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/tests/keyword_args.py Wed Feb 08 16:20:27 2017 +0100
4.3 @@ -0,0 +1,24 @@
4.4 +class C:
4.5 + def f(self, x, y, z):
4.6 + return z
4.7 +
4.8 +class D:
4.9 + def f(self, a, b, c):
4.10 + return c
4.11 +
4.12 +def xyz(obj):
4.13 + return obj.f(1, 2, z=3)
4.14 +
4.15 +def abc(obj):
4.16 + return obj.f(4, 5, c=6)
4.17 +
4.18 +c = C()
4.19 +d = D()
4.20 +
4.21 +print xyz(c) # 3
4.22 +print abc(d) # 6
4.23 +
4.24 +try:
4.25 + print xyz(d) # should raise an exception
4.26 +except TypeError:
4.27 + print "xyz(d): argument cannot be used"
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/tests/keyword_args_bad.py Wed Feb 08 16:20:27 2017 +0100
5.3 @@ -0,0 +1,15 @@
5.4 +class C:
5.5 + def f(self, x, y, z):
5.6 + return z
5.7 +
5.8 +class D:
5.9 + def f(self, a, b, c):
5.10 + return c
5.11 +
5.12 +def pqr(obj):
5.13 + return obj.f(1, 2, r=3) # no corresponding function
5.14 +
5.15 +c = C()
5.16 +d = D()
5.17 +
5.18 +print pqr(c) # should fail