1.1 --- a/deducer.py Thu Oct 06 19:28:43 2016 +0200
1.2 +++ b/deducer.py Thu Oct 06 19:29:53 2016 +0200
1.3 @@ -68,9 +68,11 @@
1.4 self.const_accesses = {}
1.5 self.const_accesses_rev = {}
1.6
1.7 - # Map usage observations to assigned attributes.
1.8 + # Map usage observations to assigned and invoked attributes.
1.9
1.10 self.assigned_attrs = {}
1.11 + self.invoked_attrs = {}
1.12 + self.invoked_attr_types = {}
1.13
1.14 # Map usage observations to objects.
1.15
1.16 @@ -82,9 +84,10 @@
1.17
1.18 self.modified_attributes = {}
1.19
1.20 - # Accesses that are assignments.
1.21 + # Accesses that are assignments or involve invocations.
1.22
1.23 self.reference_assignments = set()
1.24 + self.reference_invocations = set()
1.25
1.26 # Map locations to types, constrained indicators and attributes.
1.27
1.28 @@ -131,6 +134,7 @@
1.29 self.init_aliases()
1.30 self.init_attr_type_indexes()
1.31 self.modify_mutated_attributes()
1.32 + self.restrict_invocation_types()
1.33 self.identify_references()
1.34 self.classify_accessors()
1.35 self.classify_accesses()
1.36 @@ -529,7 +533,7 @@
1.37 referenced_attrs = self.referenced_attrs[location]
1.38
1.39 if not referenced_attrs:
1.40 - raise DeduceError, location
1.41 + raise DeduceError(location)
1.42
1.43 # Record attribute information for each name used on the
1.44 # accessor.
1.45 @@ -795,8 +799,12 @@
1.46 access_location = (path, None, attrnames, 0)
1.47
1.48 assignment = modifier == "A"
1.49 + invocation = modifier == "I"
1.50 +
1.51 if assignment:
1.52 self.reference_assignments.add(access_location)
1.53 + elif invocation:
1.54 + self.reference_invocations.add(access_location)
1.55
1.56 # Associate assignments with usage.
1.57
1.58 @@ -809,6 +817,9 @@
1.59 if assignment:
1.60 init_item(self.assigned_attrs, key, set)
1.61 self.assigned_attrs[key].add((path, name, attrnames))
1.62 + elif invocation:
1.63 + init_item(self.invoked_attrs, key, set)
1.64 + self.invoked_attrs[key].add((location, access_location))
1.65
1.66 def init_aliases(self):
1.67
1.68 @@ -925,6 +936,45 @@
1.69 self.modified_attributes[attr] = value
1.70 self.importer.set_object(attr, value.as_var())
1.71
1.72 + # Invocation refinements.
1.73 +
1.74 + def restrict_invocation_types(self):
1.75 +
1.76 + """
1.77 + Apply invocation information to the selection of types for each
1.78 + accessor.
1.79 + """
1.80 +
1.81 + for usage, locations in self.invoked_attrs.items():
1.82 + if not usage:
1.83 + continue
1.84 +
1.85 + for (location, access_location) in locations:
1.86 + path, name, attrnames, version = access_location
1.87 + if not attrnames:
1.88 + continue
1.89 +
1.90 + attrname = get_attrnames(attrnames)[0]
1.91 +
1.92 + # For each class type suggested by the usage, examine the
1.93 + # attribute provided for the attribute name.
1.94 +
1.95 + for class_type in self.get_class_types_for_usage(usage):
1.96 + ref = self.importer.get_class_attribute(class_type, attrname)
1.97 +
1.98 + # Test the attribute for being an unbound method provided by
1.99 + # the class or an ancestor.
1.100 +
1.101 + parent_class = ref and ref.parent()
1.102 +
1.103 + if ref and ref.has_kind("<function>") and (
1.104 + class_type == parent_class or class_type in self.descendants[parent_class]):
1.105 +
1.106 + # Record all class types providing the unbound method.
1.107 +
1.108 + init_item(self.invoked_attr_types, location, set)
1.109 + self.invoked_attr_types[location].add(class_type)
1.110 +
1.111 # Simplification of types.
1.112
1.113 def get_most_general_types(self, types):
1.114 @@ -1578,6 +1628,8 @@
1.115 types of the actual objects used to access attributes.
1.116 """
1.117
1.118 + path, name, version, attrnames = location
1.119 +
1.120 # Update the type details for the location.
1.121
1.122 self.provider_class_types[location].update(class_types)
1.123 @@ -1588,12 +1640,17 @@
1.124 # Instance-only and module types support only their own kinds as
1.125 # accessors.
1.126
1.127 + if self.invoked_attr_types.has_key(location):
1.128 + class_only_types = set(class_types).difference(self.invoked_attr_types[location])
1.129 + else:
1.130 + class_only_types = class_types
1.131 +
1.132 # However, the nature of accessors can be further determined.
1.133 # Any self variable may only refer to an instance.
1.134 -
1.135 - path, name, version, attrnames = location
1.136 + # Invocations of bound methods also require instances.
1.137 +
1.138 if name != "self" or not self.in_method(path):
1.139 - self.accessor_class_types[location].update(class_types)
1.140 + self.accessor_class_types[location].update(class_only_types)
1.141
1.142 if not constrained_specific:
1.143 self.accessor_instance_types[location].update(class_types)