# HG changeset patch # User Paul Boddie # Date 1489881403 -3600 # Node ID 59957b395bb15a730a881dd21cdcad12a3c842f3 # Parent 5e4ac6b12b8f87b2864b49e3be6425503b3089a6# Parent 91f416ba8ec76d53ca4f72933387921bc0e9d26a Merged changes from the default branch. diff -r 5e4ac6b12b8f -r 59957b395bb1 deducer.py --- a/deducer.py Thu Mar 16 18:13:34 2017 +0100 +++ b/deducer.py Sun Mar 19 00:56:43 2017 +0100 @@ -62,10 +62,18 @@ self.access_index = {} + # Map definition locations to affected accesses. + + self.access_index_rev = {} + # Map aliases to accesses that define them. self.alias_index = {} + # Map accesses to aliases whose initial values are influenced by them. + + self.alias_index_rev = {} + # Map constant accesses to redefined accesses. self.const_accesses = {} @@ -372,7 +380,8 @@ if referenced_attrs: attrname = get_attrname_from_location(location) - all_accessed_attrs = self.reference_all_attrs[location] + all_accessed_attrs = list(set(self.reference_all_attrs[location])) + all_accessed_attrs.sort() for attrtype, attrs in self.get_referenced_attrs(location): print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs) @@ -520,9 +529,10 @@ self.accessor_all_general_types[location] = all_general_types = \ combine_types(general_class_types, general_instance_types, general_module_types) - # Record guard information. - - if not constrained: + # Record guard information but only for accessors employed by + # accesses. There are no attribute accesses to guard, otherwise. + + if not constrained and self.access_index_rev.get(location): # Record specific type guard details. @@ -862,6 +872,13 @@ location = (path, name, None, version) locations.append(location) + # Map accessors to affected accesses. + + l = init_item(self.access_index_rev, location, set) + l.add(access_location) + + # Map accesses to supplying accessors. + self.access_index[access_location] = locations def get_accessors_for_access(self, access_location): @@ -955,22 +972,21 @@ for (path, name), all_aliases in self.importer.all_aliased_names.items(): - # For each version of the name, obtain the access location. - - for version, (original_path, original_name, attrnames, access_number) in all_aliases.items(): - accessor_location = (path, name, None, version) - access_location = (original_path, original_name, attrnames, access_number) - init_item(self.alias_index, accessor_location, list) - self.alias_index[accessor_location].append(access_location) + # For each version of the name, obtain the access locations. + + for version, aliases in all_aliases.items(): + for (original_path, original_name, attrnames, access_number) in aliases: + accessor_location = (path, name, None, version) + access_location = (original_path, original_name, attrnames, access_number) + init_item(self.alias_index, accessor_location, list) + self.alias_index[accessor_location].append(access_location) # Get aliases in terms of non-aliases and accesses. for accessor_location, access_locations in self.alias_index.items(): self.update_aliases(accessor_location, access_locations) - # Get accesses affected by aliases. - - self.alias_index_rev = {} + # Get a mapping from accesses to affected aliases. for accessor_location, access_locations in self.alias_index.items(): for access_location in access_locations: @@ -996,10 +1012,16 @@ for access_location in access_locations: (path, original_name, attrnames, access_number) = access_location + # Locations may have been recorded for return values, but they may + # not correspond to actual accesses. + + if not self.access_index.has_key(access_location): + updated_locations.add(access_location) + # Where an alias refers to a name access, obtain the original name # version details. - if attrnames is None: + elif attrnames is None: # For each name version, attempt to determine any accesses that # initialise the name. @@ -1250,10 +1272,8 @@ # Specific name-based attribute accesses. - alias_accesses = set() - for access_location, accessor_locations in self.access_index.items(): - self.record_types_for_access(access_location, accessor_locations, alias_accesses) + self.record_types_for_access(access_location, accessor_locations) # Anonymous references with attribute chains. @@ -1373,6 +1393,23 @@ if self.record_types_for_alias(location): updated_aliases.add(location) + # Define accesses employing aliases. + + alias_accesses = set() + affected_aliases = set() + + for alias in updated_aliases: + + # Access affected by the alias. + + if self.access_index_rev.has_key(alias): + alias_accesses.update(self.access_index_rev[alias]) + + # Another alias affected by the alias. + + elif self.alias_index_rev.has_key(alias): + affected_aliases.update(self.alias_index_rev[alias]) + # Update accesses employing aliases. updated_accesses = set() @@ -1381,9 +1418,7 @@ if self.record_types_for_access(access_location, self.access_index[access_location]): updated_accesses.add(access_location) - # Update aliases for updated accesses. - - affected_aliases = set() + # Determine which aliases are affected by the updated accesses. for access_location in updated_accesses: if self.alias_index_rev.has_key(access_location): @@ -1433,10 +1468,10 @@ # Detect any initialised name for the location. if name: - ref = self.get_initialised_name(location) - if ref: + refs = self.get_initialised_name(location) + if refs: (class_types, only_instance_types, module_types, - _function_types, _var_types) = separate_types([ref]) + _function_types, _var_types) = separate_types(refs) return class_types, only_instance_types, module_types, True, have_assignments # Retrieve the recorded types for the usage. @@ -1541,7 +1576,7 @@ self.referenced_attrs[location] = {} - def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None): + def record_types_for_access(self, access_location, accessor_locations): """ Define types for the 'access_location' associated with the given @@ -1568,11 +1603,6 @@ for location in accessor_locations: - # Remember accesses employing aliases. - - if alias_accesses is not None and self.alias_index.has_key(location): - alias_accesses.add(access_location) - # Use the type information deduced for names from above. if self.accessor_class_types.has_key(location): @@ -1676,6 +1706,8 @@ new_accessor_instance_types = set() new_accessor_module_types = set() + refs = set() + for access_location in self.alias_index[accessor_location]: location, name, attrnames, access_number = access_location invocation = self.reference_invocations.get(access_location) @@ -1742,52 +1774,67 @@ new_accessor_instance_types.update(instance_types) new_accessor_module_types.update(module_types) + refs.update(accessor_attrs) + # Alias references a name, not an access. else: - # Attempt to refine the types using initialised names. - - attr = self.get_initialised_name(access_location) - if attr: - attrs = [attr] + # Attempt to refine the types using initialised names or + # accessors. + + attrs = self.get_initialised_name(access_location) + + if attrs: provider_attrs = self.convert_invocation_providers(attrs, invocation) - - (class_types, instance_types, module_types, function_types, - var_types) = separate_types(provider_attrs) - - class_types = set(provider_class_types).intersection(class_types) - instance_types = set(provider_instance_types).intersection(instance_types) - module_types = set(provider_module_types).intersection(module_types) - - new_provider_class_types.update(class_types) - new_provider_instance_types.update(instance_types) - new_provider_module_types.update(module_types) - accessor_attrs = self.convert_invocations(attrs, invocation) - - (class_types, instance_types, module_types, function_types, - var_types) = separate_types(accessor_attrs) - - class_types = set(accessor_class_types).intersection(class_types) - instance_types = set(accessor_instance_types).intersection(instance_types) - module_types = set(accessor_module_types).intersection(module_types) - - new_accessor_class_types.update(class_types) - new_accessor_instance_types.update(instance_types) - new_accessor_module_types.update(module_types) + else: + provider_attrs = self.get_provider_references(access_location) + attrs = accessor_attrs = self.get_accessor_references(access_location) # Where no further information is found, do not attempt to # refine the defined accessor types. - else: + if not attrs: return False + (class_types, instance_types, module_types, function_types, + var_types) = separate_types(provider_attrs) + + class_types = set(provider_class_types).intersection(class_types) + instance_types = set(provider_instance_types).intersection(instance_types) + module_types = set(provider_module_types).intersection(module_types) + + new_provider_class_types.update(class_types) + new_provider_instance_types.update(instance_types) + new_provider_module_types.update(module_types) + + (class_types, instance_types, module_types, function_types, + var_types) = separate_types(accessor_attrs) + + class_types = set(accessor_class_types).intersection(class_types) + instance_types = set(accessor_instance_types).intersection(instance_types) + module_types = set(accessor_module_types).intersection(module_types) + + new_accessor_class_types.update(class_types) + new_accessor_instance_types.update(instance_types) + new_accessor_module_types.update(module_types) + + refs.update(accessor_attrs) + + # Update the alias relationships for invocations. + + self.update_alias_accesses(access_location, attrs) + # Record refined type details for the alias as an accessor. self.init_definition_details(accessor_location) self.update_provider_types(accessor_location, new_provider_class_types, new_provider_instance_types, new_provider_module_types) self.update_accessor_types(accessor_location, new_accessor_class_types, new_accessor_instance_types, new_accessor_module_types) + # Record reference details for the alias separately from accessors. + + self.referenced_objects[accessor_location] = refs + return new_accessor_class_types != accessor_class_types or \ new_accessor_instance_types != accessor_instance_types or \ new_accessor_module_types != accessor_module_types @@ -1797,16 +1844,9 @@ # return value information. else: - old_refs = self.referenced_objects.get(accessor_location) refs = set() for access_location in self.alias_index[accessor_location]: - - # Obtain any redefined constant access location. - - if self.const_accesses.has_key(access_location): - access_location = self.const_accesses[access_location] - location, name, attrnames, access_number = access_location invocation = self.reference_invocations.get(access_location) @@ -1827,8 +1867,14 @@ # Obtain references and attribute types for the access. attrs = self.get_references_for_access(access_location) - attrs = self.convert_invocations(attrs, invocation) - refs.update(attrs) + + # Where no further information is found, do not attempt to + # refine the defined accessor types. + + if not attrs: + return False + + refs.update(self.convert_invocations(attrs, invocation)) # Alias references a name, not an access. @@ -1836,25 +1882,83 @@ # Obtain initialiser information. - attr = self.get_initialised_name(access_location) - if attr: - refs.update(self.convert_invocations([attr], invocation)) - - # Obtain provider information. - - elif self.provider_class_types.has_key(access_location): - class_types = self.provider_class_types[access_location] - instance_types = self.provider_instance_types[access_location] - module_types = self.provider_module_types[access_location] - - types = combine_types(class_types, instance_types, module_types) - refs.update(self.convert_invocation_providers(types, invocation)) + attrs = self.get_initialised_name(access_location) + + if attrs: + provider_attrs = self.convert_invocation_providers(attrs, invocation) + accessor_attrs = self.convert_invocations(attrs, invocation) + else: + provider_attrs = self.get_provider_references(access_location) + attrs = accessor_attrs = self.get_accessor_references(access_location) + + # Where no further information is found, do not attempt to + # refine the defined accessor types. + + if not attrs: + return False + + refs.update(self.convert_invocations(attrs, invocation)) + + # Update the alias relationships for invocations. + + self.update_alias_accesses(access_location, attrs) + + # Record refined type details for the alias as an accessor. + + (class_types, instance_types, module_types, function_types, + var_types) = separate_types(refs) + + # Where non-accessor types are found, do not attempt to refine + # the defined accessor types. + + if not function_types and not var_types: + self.init_definition_details(accessor_location) + self.update_provider_types(accessor_location, class_types + instance_types, class_types + instance_types, module_types) + self.update_accessor_types(accessor_location, class_types, instance_types, module_types) # Record reference details for the alias separately from accessors. self.referenced_objects[accessor_location] = refs - return old_refs != refs + return True + + def update_alias_accesses(self, access_location, refs): + + """ + Record 'access_location' as a location affected by the return values of + 'refs' if an invocation is involved. + """ + + if not self.reference_invocations.has_key(access_location): + return + + for ref in refs: + + # Initialising accesses originate from the return values. + + key = (ref.get_origin(), "$return") + self._update_alias_accesses(access_location, key, self.importer.all_initialised_names) + self._update_alias_accesses(access_location, key, self.importer.all_aliased_names) + + def _update_alias_accesses(self, access_location, key, names): + + """ + Make each return value provide information to the given + 'access_location', using 'key' to reference the return value and 'names' + as the collection of definitions. + """ + + versions = names.get(key) + if not versions: + return + + for version in versions.keys(): + location = key + (None, version) + l = init_item(self.alias_index_rev, location, set) + l.add(access_location) + + l = init_item(self.alias_index, access_location, set) + l.add(location) def get_references_for_access(self, access_location): @@ -1878,11 +1982,20 @@ providers = set() for ref in refs: - ref = self.convert_invocation_provider(ref) + invocation_providers = self.convert_accessors_to_providers(self.convert_invocation_provider(ref)) + providers.update(invocation_providers) + + return self.references_or_var(providers) + + def convert_accessors_to_providers(self, refs): + + "Convert accessor 'refs' to provider references." + + providers = set() + for ref in refs: if ref.has_kind(""): providers.add(Reference("", ref.get_origin())) providers.add(ref) - return providers def convert_invocation_provider(self, ref): @@ -1891,11 +2004,11 @@ if ref: if ref.has_kind(""): - return ref + return [ref] elif ref.has_kind(""): return self.convert_function_invocation(ref) - return Reference("") + return [Reference("")] def convert_invocations(self, refs, invocation): @@ -1904,7 +2017,15 @@ value. """ - return invocation and map(self.convert_invocation, refs) or refs + if not invocation: + return refs + + invocation_refs = set() + + for ref in refs: + invocation_refs.update(self.convert_invocation(ref)) + + return self.references_or_var(invocation_refs) def convert_invocation(self, ref): @@ -1912,23 +2033,32 @@ if ref: if ref.has_kind(""): - return ref.instance_of() + return [ref.instance_of()] elif ref.has_kind(""): return self.convert_function_invocation(ref) - return Reference("") + return [Reference("")] def convert_function_invocation(self, ref): "Convert the function 'ref' to its return value reference." - initialised_names = self.importer.all_initialised_names.get((ref.get_origin(), "$return")) - if initialised_names: - refs = set(initialised_names.values()) - if len(refs) == 1: - return first(refs) - - return Reference("") + initialisers = self.importer.all_initialised_names.get((ref.get_origin(), "$return")) + aliases = self.importer.all_aliased_names.get((ref.get_origin(), "$return")) + + if initialisers and not aliases: + return set(initialisers.values()) + + return [Reference("")] + + def references_or_var(self, refs): + + var = Reference("") + + if var in refs: + return set([var]) + else: + return refs def get_initialised_name(self, access_location): @@ -1941,9 +2071,39 @@ # Use initialiser information, if available. - refs = self.importer.all_initialised_names.get((path, name)) - if refs and refs.has_key(version): - return refs[version] + initialisers = self.importer.all_initialised_names.get((path, name)) + if initialisers and initialisers.has_key(version): + return [initialisers[version]] + else: + return None + + def get_accessor_references(self, access_location): + + """ + Return references corresponding to accessor details at the given + 'access_location'. + """ + + if self.accessor_class_types.has_key(access_location): + class_types = self.accessor_class_types[access_location] + instance_types = self.accessor_instance_types[access_location] + module_types = self.accessor_module_types[access_location] + return combine_types(class_types, instance_types, module_types) + else: + return None + + def get_provider_references(self, access_location): + + """ + Return references corresponding to provider details at the given + 'access_location'. + """ + + if self.provider_class_types.has_key(access_location): + class_types = self.provider_class_types[access_location] + instance_types = self.provider_instance_types[access_location] + module_types = self.provider_module_types[access_location] + return combine_types(class_types, instance_types, module_types) else: return None diff -r 5e4ac6b12b8f -r 59957b395bb1 importer.py --- a/importer.py Thu Mar 16 18:13:34 2017 +0100 +++ b/importer.py Sun Mar 19 00:56:43 2017 +0100 @@ -116,6 +116,10 @@ self.all_constants = {} self.all_constant_values = {} + # Common function value collection used during inspection. + + self.all_return_values = {} + self.make_cache() def give_warning(self, name): diff -r 5e4ac6b12b8f -r 59957b395bb1 inspector.py --- a/inspector.py Thu Mar 16 18:13:34 2017 +0100 +++ b/inspector.py Sun Mar 19 00:56:43 2017 +0100 @@ -1487,7 +1487,9 @@ "Record the given return 'expr'." path = self.get_namespace_path() - init_item(self.return_values, path, list) - self.return_values[path].append(expr) + l = init_item(self.return_values, path, list) + l.append(expr) + if not self.importer.all_return_values.has_key(path): + self.importer.all_return_values[path] = l # vim: tabstop=4 expandtab shiftwidth=4 diff -r 5e4ac6b12b8f -r 59957b395bb1 modules.py --- a/modules.py Thu Mar 16 18:13:34 2017 +0100 +++ b/modules.py Sun Mar 19 00:56:43 2017 +0100 @@ -71,10 +71,6 @@ self.exception_namespaces = set() - # Return value details. - - self.return_values = {} - # Attribute usage at module and function levels. self.attr_usage = {} @@ -102,6 +98,10 @@ self.initialised_names = {} self.aliased_names = {} + # Return values for functions in this module. + + self.return_values = {} + def __repr__(self): return "BasicModule(%r, %r)" % (self.name, self.importer) @@ -496,7 +496,9 @@ init_item(self.aliased_names, (path, name), dict) if number == "{}": number = None else: number = int(number) - self.aliased_names[(path, name)][int(version)] = (original_path, original_name, attrnames != "{}" and attrnames or None, number) + d = self.aliased_names[(path, name)] + init_item(d, int(version), list) + d[int(version)].append((original_path, original_name, attrnames != "{}" and attrnames or None, number)) line = f.readline().rstrip() def _get_function_parameters(self, f): @@ -763,9 +765,10 @@ for (path, name), aliases in assignments: versions = aliases.items() versions.sort() - for version, alias in versions: - original_path, original_name, attrnames, number = alias - print >>f, path, name, version, original_path, original_name, attrnames or "{}", number is None and "{}" or number + for version, version_aliases in versions: + for alias in version_aliases: + original_path, original_name, attrnames, number = alias + print >>f, path, name, version, original_path, original_name, attrnames or "{}", number is None and "{}" or number print >>f print >>f, "function parameters:" diff -r 5e4ac6b12b8f -r 59957b395bb1 resolving.py --- a/resolving.py Thu Mar 16 18:13:34 2017 +0100 +++ b/resolving.py Sun Mar 19 00:56:43 2017 +0100 @@ -244,11 +244,11 @@ aliased_names = {} for i, name_ref in enumerate(values): - initialised_ref, aliased_name = self.resolve_reference(path, name_ref) + initialised_ref, _aliased_names = self.resolve_reference(path, name_ref) if initialised_ref: initialised_names[i] = initialised_ref - if aliased_name: - aliased_names[i] = aliased_name + if _aliased_names: + aliased_names[i] = _aliased_names if initialised_names: self.initialised_names[(path, name)] = initialised_names @@ -269,11 +269,11 @@ aliased_names = {} for i, name_ref in enumerate(values): - initialised_ref, aliased_name = self.resolve_reference(path, name_ref) + initialised_ref, _aliased_names = self.resolve_reference(path, name_ref) if initialised_ref: initialised_names[i] = initialised_ref - if aliased_name: - aliased_names[i] = aliased_name + if _aliased_names: + aliased_names[i] = _aliased_names if initialised_names: self.initialised_names[(path, "$return")] = initialised_names @@ -290,7 +290,7 @@ const_accesses = self.const_accesses.get(path) initialised_ref = None - aliased_name = None + aliased_names = None no_reference = None, None # Unwrap invocations. @@ -336,9 +336,9 @@ # alongside original name, attribute and access # number details. - aliased_name = path, name_ref.original_name, name_ref.attrnames, name_ref.number + aliased_names = [(path, name_ref.original_name, name_ref.attrnames, name_ref.number)] - return None, aliased_name + return None, aliased_names # Attempt to resolve a plain name reference. @@ -355,9 +355,9 @@ # alongside original name, attribute and access # number details. - aliased_name = path, name_ref.name, None, name_ref.number + aliased_names = [(path, name_ref.name, None, name_ref.number)] - return None, aliased_name + return None, aliased_names ref = self.get_resolved_object(ref.get_origin()) if not ref: @@ -382,13 +382,32 @@ # Convert class invocations to instances. - if ref and invocation or ref.has_kind(""): - ref = self.convert_invocation(ref) + if ref and (invocation or ref.has_kind("")): + target_ref = ref + ref = self.convert_invocation(target_ref) + + if not ref or ref.has_kind(""): + aliased_names = self.get_aliases_for_target(target_ref.get_origin()) + else: + initialised_ref = ref - if ref and not ref.has_kind(""): + elif ref and not ref.has_kind(""): initialised_ref = ref + + return initialised_ref, aliased_names - return initialised_ref, aliased_name + def get_aliases_for_target(self, path): + + "Return a list of return value locations for the given 'path'." + + return_values = self.importer.all_return_values.get(path) + locations = [] + + if return_values: + for version in range(0, len(return_values)): + locations.append((path, "$return", None, version)) + + return locations def resolve_literals(self):