# HG changeset patch # User Paul Boddie # Date 1486567227 -3600 # Node ID aa11ebd14c79eac3a5bcda5c18d8593f0aaa665d # Parent e8e4490ac9d40c80f1b630b3623d4d02d29fba5b Incorporated keyword arguments into invocation descriptions so that argument compatibility can be more broadly tested and restrict accesses accordingly. diff -r e8e4490ac9d4 -r aa11ebd14c79 deducer.py --- a/deducer.py Wed Feb 08 01:21:39 2017 +0100 +++ b/deducer.py Wed Feb 08 16:20:27 2017 +0100 @@ -1863,8 +1863,8 @@ 'object_type' identified by 'ref' is compatible with any arguments used. """ - arguments = self.reference_invocations.get(location) - if arguments is None: + invocation = self.reference_invocations.get(location) + if invocation is None: return True objpath = ref.get_origin() @@ -1876,12 +1876,15 @@ return True defaults = self.importer.function_defaults.get(objpath) + arguments, keywords = invocation + names = set(parameters) # Determine whether the specified arguments are # compatible with the callable signature. if arguments >= len(parameters) - len(defaults) and \ - arguments <= len(parameters): + arguments <= len(parameters) and \ + names.issuperset(keywords): return True else: diff -r e8e4490ac9d4 -r aa11ebd14c79 encoders.py --- a/encoders.py Wed Feb 08 01:21:39 2017 +0100 +++ b/encoders.py Wed Feb 08 16:20:27 2017 +0100 @@ -93,7 +93,8 @@ if assignment: return "=" elif invocation is not None: - return "(%d)" % invocation + arguments, keywords = invocation + return "(%d;%s)" % (arguments, ",".join(keywords)) else: return "_" @@ -111,8 +112,13 @@ modifiers.append((True, None)) i += 1 elif s[i] == "(": + j = s.index(";", i) + arguments = int(s[i+1:j]) + i = j j = s.index(")", i) - modifiers.append((False, int(s[i+1:j]))) + keywords = s[i+1:j] + keywords = keywords and keywords.split(",") or [] + modifiers.append((False, (arguments, keywords))) i = j + 1 else: modifiers.append((False, None)) diff -r e8e4490ac9d4 -r aa11ebd14c79 inspector.py --- a/inspector.py Wed Feb 08 01:21:39 2017 +0100 +++ b/inspector.py Wed Feb 08 16:20:27 2017 +0100 @@ -438,7 +438,8 @@ # Record attribute usage in the tracker, and record the branch # information for the access. - branches = tracker.use_attribute(name, attrname, self.in_invocation is not None, assignment) + branches = tracker.use_attribute(name, attrname, + self.in_invocation is not None, assignment) if not branches: raise InspectError("Name %s is accessed using %s before an assignment." % ( @@ -742,22 +743,29 @@ self.allocate_arguments(path, n.args) try: + in_invocation = self.in_invocation + self.in_invocation = None + + # Process the arguments. + + keywords = set() + + for arg in n.args: + self.process_structure_node(arg) + if isinstance(arg, compiler.ast.Keyword): + keywords.add(arg.name) + + keywords = list(keywords) + keywords.sort() + # Communicate to the invocation target expression that it forms the # target of an invocation, potentially affecting attribute accesses. - in_invocation = self.in_invocation - self.in_invocation = len(n.args) + self.in_invocation = len(n.args), keywords # Process the expression, obtaining any identified reference. name_ref = self.process_structure_node(n.node) - self.in_invocation = None - - # Process the arguments. - - for arg in n.args: - self.process_structure_node(arg) - self.in_invocation = in_invocation # Detect class invocations. diff -r e8e4490ac9d4 -r aa11ebd14c79 tests/keyword_args.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/keyword_args.py Wed Feb 08 16:20:27 2017 +0100 @@ -0,0 +1,24 @@ +class C: + def f(self, x, y, z): + return z + +class D: + def f(self, a, b, c): + return c + +def xyz(obj): + return obj.f(1, 2, z=3) + +def abc(obj): + return obj.f(4, 5, c=6) + +c = C() +d = D() + +print xyz(c) # 3 +print abc(d) # 6 + +try: + print xyz(d) # should raise an exception +except TypeError: + print "xyz(d): argument cannot be used" diff -r e8e4490ac9d4 -r aa11ebd14c79 tests/keyword_args_bad.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/keyword_args_bad.py Wed Feb 08 16:20:27 2017 +0100 @@ -0,0 +1,15 @@ +class C: + def f(self, x, y, z): + return z + +class D: + def f(self, a, b, c): + return c + +def pqr(obj): + return obj.f(1, 2, r=3) # no corresponding function + +c = C() +d = D() + +print pqr(c) # should fail