# HG changeset patch # User Paul Boddie # Date 1475774993 -7200 # Node ID 6cc807f909843c5e7f18cb821439081542ce5411 # Parent 1b764ef8a18401918a79f6b624b9958ecf43dd0e Added some support for eliminating accessor class types where the provided attributes are invoked and are unbound methods. diff -r 1b764ef8a184 -r 6cc807f90984 deducer.py --- a/deducer.py Thu Oct 06 19:28:43 2016 +0200 +++ b/deducer.py Thu Oct 06 19:29:53 2016 +0200 @@ -68,9 +68,11 @@ self.const_accesses = {} self.const_accesses_rev = {} - # Map usage observations to assigned attributes. + # Map usage observations to assigned and invoked attributes. self.assigned_attrs = {} + self.invoked_attrs = {} + self.invoked_attr_types = {} # Map usage observations to objects. @@ -82,9 +84,10 @@ self.modified_attributes = {} - # Accesses that are assignments. + # Accesses that are assignments or involve invocations. self.reference_assignments = set() + self.reference_invocations = set() # Map locations to types, constrained indicators and attributes. @@ -131,6 +134,7 @@ self.init_aliases() self.init_attr_type_indexes() self.modify_mutated_attributes() + self.restrict_invocation_types() self.identify_references() self.classify_accessors() self.classify_accesses() @@ -529,7 +533,7 @@ referenced_attrs = self.referenced_attrs[location] if not referenced_attrs: - raise DeduceError, location + raise DeduceError(location) # Record attribute information for each name used on the # accessor. @@ -795,8 +799,12 @@ access_location = (path, None, attrnames, 0) assignment = modifier == "A" + invocation = modifier == "I" + if assignment: self.reference_assignments.add(access_location) + elif invocation: + self.reference_invocations.add(access_location) # Associate assignments with usage. @@ -809,6 +817,9 @@ if assignment: init_item(self.assigned_attrs, key, set) self.assigned_attrs[key].add((path, name, attrnames)) + elif invocation: + init_item(self.invoked_attrs, key, set) + self.invoked_attrs[key].add((location, access_location)) def init_aliases(self): @@ -925,6 +936,45 @@ self.modified_attributes[attr] = value self.importer.set_object(attr, value.as_var()) + # Invocation refinements. + + def restrict_invocation_types(self): + + """ + Apply invocation information to the selection of types for each + accessor. + """ + + for usage, locations in self.invoked_attrs.items(): + if not usage: + continue + + for (location, access_location) in locations: + path, name, attrnames, version = access_location + if not attrnames: + continue + + attrname = get_attrnames(attrnames)[0] + + # For each class type suggested by the usage, examine the + # attribute provided for the attribute name. + + for class_type in self.get_class_types_for_usage(usage): + ref = self.importer.get_class_attribute(class_type, attrname) + + # Test the attribute for being an unbound method provided by + # the class or an ancestor. + + parent_class = ref and ref.parent() + + if ref and ref.has_kind("") and ( + class_type == parent_class or class_type in self.descendants[parent_class]): + + # Record all class types providing the unbound method. + + init_item(self.invoked_attr_types, location, set) + self.invoked_attr_types[location].add(class_type) + # Simplification of types. def get_most_general_types(self, types): @@ -1578,6 +1628,8 @@ types of the actual objects used to access attributes. """ + path, name, version, attrnames = location + # Update the type details for the location. self.provider_class_types[location].update(class_types) @@ -1588,12 +1640,17 @@ # Instance-only and module types support only their own kinds as # accessors. + if self.invoked_attr_types.has_key(location): + class_only_types = set(class_types).difference(self.invoked_attr_types[location]) + else: + class_only_types = class_types + # However, the nature of accessors can be further determined. # Any self variable may only refer to an instance. - - path, name, version, attrnames = location + # Invocations of bound methods also require instances. + if name != "self" or not self.in_method(path): - self.accessor_class_types[location].update(class_types) + self.accessor_class_types[location].update(class_only_types) if not constrained_specific: self.accessor_instance_types[location].update(class_types)