1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/deducer.py Thu Sep 08 22:49:00 2016 +0200
1.3 @@ -0,0 +1,1696 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Deduce types for usage observations.
1.8 +
1.9 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from common import first, get_attrname_from_location, get_attrnames, \
1.26 + init_item, make_key, sorted_output, \
1.27 + CommonOutput
1.28 +from encoders import encode_attrnames, encode_access_location, \
1.29 + encode_constrained, encode_location
1.30 +from os.path import join
1.31 +from referencing import Reference
1.32 +
1.33 +class Deducer(CommonOutput):
1.34 +
1.35 + "Deduce types in a program."
1.36 +
1.37 + def __init__(self, importer, output):
1.38 +
1.39 + """
1.40 + Initialise an instance using the given 'importer' that will perform
1.41 + deductions on the program information, writing the results to the given
1.42 + 'output' directory.
1.43 + """
1.44 +
1.45 + self.importer = importer
1.46 + self.output = output
1.47 +
1.48 + # Descendants of classes.
1.49 +
1.50 + self.descendants = {}
1.51 + self.init_descendants()
1.52 + self.init_special_attributes()
1.53 +
1.54 + # Map locations to usage in order to determine specific types.
1.55 +
1.56 + self.location_index = {}
1.57 +
1.58 + # Map access locations to definition locations.
1.59 +
1.60 + self.access_index = {}
1.61 +
1.62 + # Map aliases to accesses that define them.
1.63 +
1.64 + self.alias_index = {}
1.65 +
1.66 + # Map usage observations to assigned attributes.
1.67 +
1.68 + self.assigned_attrs = {}
1.69 +
1.70 + # Map usage observations to objects.
1.71 +
1.72 + self.attr_class_types = {}
1.73 + self.attr_instance_types = {}
1.74 + self.attr_module_types = {}
1.75 +
1.76 + # Modified attributes from usage observations.
1.77 +
1.78 + self.modified_attributes = {}
1.79 +
1.80 + # Map locations to types, constrained indicators and attributes.
1.81 +
1.82 + self.accessor_class_types = {}
1.83 + self.accessor_instance_types = {}
1.84 + self.accessor_module_types = {}
1.85 + self.provider_class_types = {}
1.86 + self.provider_instance_types = {}
1.87 + self.provider_module_types = {}
1.88 + self.reference_constrained = set()
1.89 + self.access_constrained = set()
1.90 + self.referenced_attrs = {}
1.91 + self.referenced_objects = {}
1.92 +
1.93 + # Accumulated information about accessors and providers.
1.94 +
1.95 + self.accessor_general_class_types = {}
1.96 + self.accessor_general_instance_types = {}
1.97 + self.accessor_general_module_types = {}
1.98 + self.accessor_all_types = {}
1.99 + self.accessor_all_general_types = {}
1.100 + self.provider_all_types = {}
1.101 + self.accessor_guard_tests = {}
1.102 +
1.103 + # Accumulated information about accessed attributes and
1.104 + # attribute-specific accessor tests.
1.105 +
1.106 + self.reference_class_attrs = {}
1.107 + self.reference_instance_attrs = {}
1.108 + self.reference_module_attrs = {}
1.109 + self.reference_all_attrs = {}
1.110 + self.reference_all_attrtypes = {}
1.111 + self.reference_test_types = {}
1.112 + self.reference_test_accessor_types = {}
1.113 +
1.114 + # The processing workflow itself.
1.115 +
1.116 + self.init_usage_index()
1.117 + self.init_accessors()
1.118 + self.init_accesses()
1.119 + self.init_aliases()
1.120 + self.init_attr_type_indexes()
1.121 + self.modify_mutated_attributes()
1.122 + self.identify_references()
1.123 + self.classify_accessors()
1.124 + self.classify_accesses()
1.125 +
1.126 + def to_output(self):
1.127 +
1.128 + "Write the output files using deduction information."
1.129 +
1.130 + self.check_output()
1.131 +
1.132 + self.write_mutations()
1.133 + self.write_accessors()
1.134 + self.write_accesses()
1.135 +
1.136 + def write_mutations(self):
1.137 +
1.138 + """
1.139 + Write mutation-related output in the following format:
1.140 +
1.141 + qualified name " " original object type
1.142 +
1.143 + Object type can be "<class>", "<function>" or "<var>".
1.144 + """
1.145 +
1.146 + f = open(join(self.output, "mutations"), "w")
1.147 + try:
1.148 + attrs = self.modified_attributes.items()
1.149 + attrs.sort()
1.150 +
1.151 + for attr, value in attrs:
1.152 + print >>f, attr, value
1.153 + finally:
1.154 + f.close()
1.155 +
1.156 + def write_accessors(self):
1.157 +
1.158 + """
1.159 + Write reference-related output in the following format for types:
1.160 +
1.161 + location " " ( "constrained" | "deduced" ) " " attribute type " " most general type names " " number of specific types
1.162 +
1.163 + Note that multiple lines can be given for each location, one for each
1.164 + attribute type.
1.165 +
1.166 + Locations have the following format:
1.167 +
1.168 + qualified name of scope "." local name ":" name version
1.169 +
1.170 + The attribute type can be "<class>", "<instance>", "<module>" or "<>",
1.171 + where the latter indicates an absence of suitable references.
1.172 +
1.173 + Type names indicate the type providing the attributes, being either a
1.174 + class or module qualified name.
1.175 +
1.176 + ----
1.177 +
1.178 + A summary of accessor types is formatted as follows:
1.179 +
1.180 + location " " ( "constrained" | "deduced" ) " " ( "specific" | "common" | "unguarded" ) " " most general type names " " number of specific types
1.181 +
1.182 + This summary groups all attribute types (class, instance, module) into a
1.183 + single line in order to determine the complexity of identifying an
1.184 + accessor.
1.185 +
1.186 + ----
1.187 +
1.188 + References that cannot be supported by any types are written to a
1.189 + warnings file in the following format:
1.190 +
1.191 + location
1.192 +
1.193 + ----
1.194 +
1.195 + For each location where a guard would be asserted to guarantee the
1.196 + nature of an object, the following format is employed:
1.197 +
1.198 + location " " ( "specific" | "common" ) " " object kind " " object types
1.199 +
1.200 + Object kind can be "<class>", "<instance>" or "<module>".
1.201 + """
1.202 +
1.203 + f_type_summary = open(join(self.output, "type_summary"), "w")
1.204 + f_types = open(join(self.output, "types"), "w")
1.205 + f_warnings = open(join(self.output, "type_warnings"), "w")
1.206 + f_guards = open(join(self.output, "guards"), "w")
1.207 +
1.208 + try:
1.209 + locations = self.accessor_class_types.keys()
1.210 + locations.sort()
1.211 +
1.212 + for location in locations:
1.213 + constrained = location in self.reference_constrained
1.214 +
1.215 + # Accessor information.
1.216 +
1.217 + class_types = self.accessor_class_types[location]
1.218 + instance_types = self.accessor_instance_types[location]
1.219 + module_types = self.accessor_module_types[location]
1.220 +
1.221 + general_class_types = self.accessor_general_class_types[location]
1.222 + general_instance_types = self.accessor_general_instance_types[location]
1.223 + general_module_types = self.accessor_general_module_types[location]
1.224 +
1.225 + all_types = self.accessor_all_types[location]
1.226 + all_general_types = self.accessor_all_general_types[location]
1.227 +
1.228 + if class_types:
1.229 + print >>f_types, encode_location(location), encode_constrained(constrained), "<class>", \
1.230 + sorted_output(general_class_types), len(class_types)
1.231 +
1.232 + if instance_types:
1.233 + print >>f_types, encode_location(location), encode_constrained(constrained), "<instance>", \
1.234 + sorted_output(general_instance_types), len(instance_types)
1.235 +
1.236 + if module_types:
1.237 + print >>f_types, encode_location(location), encode_constrained(constrained), "<module>", \
1.238 + sorted_output(general_module_types), len(module_types)
1.239 +
1.240 + if not all_types:
1.241 + print >>f_types, encode_location(location), "deduced", "<>", 0
1.242 + print >>f_warnings, encode_location(location)
1.243 +
1.244 + guard_test = self.accessor_guard_tests.get(location)
1.245 +
1.246 + # Write specific type guard details.
1.247 +
1.248 + if guard_test and guard_test.startswith("specific"):
1.249 + print >>f_guards, encode_location(location), guard_test, \
1.250 + self.get_kinds(all_types)[0], \
1.251 + sorted_output(all_types)
1.252 +
1.253 + # Write common type guard details.
1.254 +
1.255 + elif guard_test and guard_test.startswith("common"):
1.256 + print >>f_guards, encode_location(location), guard_test, \
1.257 + self.get_kinds(all_general_types)[0], \
1.258 + sorted_output(all_general_types)
1.259 +
1.260 + print >>f_type_summary, encode_location(location), encode_constrained(constrained), \
1.261 + guard_test or "unguarded", sorted_output(all_general_types), len(all_types)
1.262 +
1.263 + finally:
1.264 + f_type_summary.close()
1.265 + f_types.close()
1.266 + f_warnings.close()
1.267 + f_guards.close()
1.268 +
1.269 + def write_accesses(self):
1.270 +
1.271 + """
1.272 + Specific attribute output is produced in the following format:
1.273 +
1.274 + location " " ( "constrained" | "deduced" ) " " attribute type " " attribute references
1.275 +
1.276 + Note that multiple lines can be given for each location and attribute
1.277 + name, one for each attribute type.
1.278 +
1.279 + Locations have the following format:
1.280 +
1.281 + qualified name of scope "." local name " " attribute name ":" access number
1.282 +
1.283 + The attribute type can be "<class>", "<instance>", "<module>" or "<>",
1.284 + where the latter indicates an absence of suitable references.
1.285 +
1.286 + Attribute references have the following format:
1.287 +
1.288 + object type ":" qualified name
1.289 +
1.290 + Object type can be "<class>", "<function>" or "<var>".
1.291 +
1.292 + ----
1.293 +
1.294 + A summary of attributes is formatted as follows:
1.295 +
1.296 + location " " attribute name " " ( "constrained" | "deduced" ) " " test " " attribute references
1.297 +
1.298 + This summary groups all attribute types (class, instance, module) into a
1.299 + single line in order to determine the complexity of each access.
1.300 +
1.301 + Tests can be "validate", "specific", "untested", "guarded-validate" or "guarded-specific".
1.302 +
1.303 + ----
1.304 +
1.305 + For each access where a test would be asserted to guarantee the
1.306 + nature of an attribute, the following formats are employed:
1.307 +
1.308 + location " " attribute name " " "validate"
1.309 + location " " attribute name " " "specific" " " attribute type " " object type
1.310 +
1.311 + ----
1.312 +
1.313 + References that cannot be supported by any types are written to a
1.314 + warnings file in the following format:
1.315 +
1.316 + location
1.317 + """
1.318 +
1.319 + f_attr_summary = open(join(self.output, "attribute_summary"), "w")
1.320 + f_attrs = open(join(self.output, "attributes"), "w")
1.321 + f_tests = open(join(self.output, "tests"), "w")
1.322 + f_warnings = open(join(self.output, "attribute_warnings"), "w")
1.323 +
1.324 + try:
1.325 + locations = self.referenced_attrs.keys()
1.326 + locations.sort()
1.327 +
1.328 + for location in locations:
1.329 + constrained = location in self.access_constrained
1.330 +
1.331 + # Attribute information, both name-based and anonymous.
1.332 +
1.333 + referenced_attrs = self.referenced_attrs[location]
1.334 +
1.335 + if referenced_attrs:
1.336 + attrname = get_attrname_from_location(location)
1.337 +
1.338 + all_accessed_attrs = self.reference_all_attrs[location]
1.339 +
1.340 + for attrtype, attrs in self.get_referenced_attrs(location):
1.341 + print >>f_attrs, encode_access_location(location), encode_constrained(constrained), attrtype, sorted_output(attrs)
1.342 +
1.343 + test_type = self.reference_test_types.get(location)
1.344 +
1.345 + # Write the need to test at run time.
1.346 +
1.347 + if test_type == "validate":
1.348 + print >>f_tests, encode_access_location(location), test_type
1.349 +
1.350 + # Write any type checks for anonymous accesses.
1.351 +
1.352 + elif test_type and self.reference_test_accessor_types.get(location):
1.353 + print >>f_tests, encode_access_location(location), test_type, \
1.354 + sorted_output(all_accessed_attrs), \
1.355 + self.reference_test_accessor_types[location]
1.356 +
1.357 + print >>f_attr_summary, encode_access_location(location), encode_constrained(constrained), \
1.358 + test_type or "untested", sorted_output(all_accessed_attrs)
1.359 +
1.360 + else:
1.361 + print >>f_warnings, encode_access_location(location)
1.362 +
1.363 + finally:
1.364 + f_attr_summary.close()
1.365 + f_attrs.close()
1.366 + f_tests.close()
1.367 + f_warnings.close()
1.368 +
1.369 + def classify_accessors(self):
1.370 +
1.371 + "For each program location, classify accessors."
1.372 +
1.373 + # Where instance and module types are defined, class types are also
1.374 + # defined. See: init_definition_details
1.375 +
1.376 + locations = self.accessor_class_types.keys()
1.377 +
1.378 + for location in locations:
1.379 + constrained = location in self.reference_constrained
1.380 +
1.381 + # Provider information.
1.382 +
1.383 + class_types = self.provider_class_types[location]
1.384 + instance_types = self.provider_instance_types[location]
1.385 + module_types = self.provider_module_types[location]
1.386 +
1.387 + # Collect specific and general type information.
1.388 +
1.389 + self.provider_all_types[location] = all_types = \
1.390 + self.combine_types(class_types, instance_types, module_types)
1.391 +
1.392 + # Accessor information.
1.393 +
1.394 + class_types = self.accessor_class_types[location]
1.395 + self.accessor_general_class_types[location] = \
1.396 + general_class_types = self.get_most_general_types(class_types)
1.397 +
1.398 + instance_types = self.accessor_instance_types[location]
1.399 + self.accessor_general_instance_types[location] = \
1.400 + general_instance_types = self.get_most_general_types(instance_types)
1.401 +
1.402 + module_types = self.accessor_module_types[location]
1.403 + self.accessor_general_module_types[location] = \
1.404 + general_module_types = self.get_most_general_module_types(module_types)
1.405 +
1.406 + # Collect specific and general type information.
1.407 +
1.408 + self.accessor_all_types[location] = all_types = \
1.409 + self.combine_types(class_types, instance_types, module_types)
1.410 +
1.411 + self.accessor_all_general_types[location] = all_general_types = \
1.412 + self.combine_types(general_class_types, general_instance_types, general_module_types)
1.413 +
1.414 + # Record guard information.
1.415 +
1.416 + if not constrained:
1.417 +
1.418 + # Record specific type guard details.
1.419 +
1.420 + if len(all_types) == 1:
1.421 + self.accessor_guard_tests[location] = self.test_for_types("specific", all_types)
1.422 + elif self.is_single_class_type(all_types):
1.423 + self.accessor_guard_tests[location] = "specific-object"
1.424 +
1.425 + # Record common type guard details.
1.426 +
1.427 + elif len(all_general_types) == 1:
1.428 + self.accessor_guard_tests[location] = self.test_for_types("common", all_types)
1.429 + elif self.is_single_class_type(all_general_types):
1.430 + self.accessor_guard_tests[location] = "common-object"
1.431 +
1.432 + # Otherwise, no convenient guard can be defined.
1.433 +
1.434 + def classify_accesses(self):
1.435 +
1.436 + "For each program location, classify accesses."
1.437 +
1.438 + # Attribute accesses use potentially different locations to those of
1.439 + # accessors.
1.440 +
1.441 + locations = self.referenced_attrs.keys()
1.442 +
1.443 + for location in locations:
1.444 + constrained = location in self.access_constrained
1.445 +
1.446 + # Determine whether the attribute access is guarded or not.
1.447 +
1.448 + accessor_locations = self.get_accessors_for_access(location)
1.449 +
1.450 + all_provider_types = set()
1.451 + all_accessor_types = set()
1.452 + all_accessor_general_types = set()
1.453 +
1.454 + for accessor_location in accessor_locations:
1.455 +
1.456 + # Obtain the provider types for guard-related attribute access
1.457 + # checks.
1.458 +
1.459 + provider_guard_types = self.provider_all_types.get(accessor_location)
1.460 + all_provider_types.update(provider_guard_types)
1.461 +
1.462 + # Obtain the accessor types.
1.463 +
1.464 + accessor_guard_types = self.accessor_all_types.get(accessor_location)
1.465 + accessor_general_guard_types = self.accessor_all_general_types.get(accessor_location)
1.466 + all_accessor_types.update(accessor_guard_types)
1.467 + all_accessor_general_types.update(accessor_general_guard_types)
1.468 +
1.469 + # Determine the basis on which the access has been guarded.
1.470 +
1.471 + guarded = (
1.472 + len(all_accessor_types) == 1 or
1.473 + self.is_single_class_type(all_accessor_types) or
1.474 + len(all_accessor_general_types) == 1 or
1.475 + self.is_single_class_type(all_accessor_general_types)
1.476 + )
1.477 +
1.478 + if guarded:
1.479 + (guard_class_types, guard_instance_types, guard_module_types,
1.480 + _function_types, _var_types) = self.separate_types(all_provider_types)
1.481 +
1.482 + # Attribute information, both name-based and anonymous.
1.483 +
1.484 + referenced_attrs = self.referenced_attrs[location]
1.485 +
1.486 + if referenced_attrs:
1.487 +
1.488 + # Record attribute information for each name used on the
1.489 + # accessor.
1.490 +
1.491 + attrname = get_attrname_from_location(location)
1.492 +
1.493 + all_accessed_attrs = set()
1.494 + all_accessed_attrtypes = set()
1.495 + all_providers = set()
1.496 + all_general_providers = set()
1.497 +
1.498 + for attrtype, d, general in [
1.499 + ("<class>", self.reference_class_attrs, self.get_most_general_types),
1.500 + ("<instance>", self.reference_instance_attrs, self.get_most_general_types),
1.501 + ("<module>", self.reference_module_attrs, self.get_most_general_module_types)]:
1.502 +
1.503 + attrs = [attr for _attrtype, object_type, attr in referenced_attrs if _attrtype == attrtype]
1.504 + providers = [object_type for _attrtype, object_type, attr in referenced_attrs if _attrtype == attrtype]
1.505 + general_providers = general(providers)
1.506 + d[location] = set(attrs)
1.507 +
1.508 + if attrs:
1.509 + all_accessed_attrs.update(attrs)
1.510 + all_accessed_attrtypes.add(attrtype)
1.511 + all_providers.update(providers)
1.512 + all_general_providers.update(general_providers)
1.513 +
1.514 + # Determine which attributes would be provided by the
1.515 + # accessor types upheld by a guard.
1.516 +
1.517 + if guarded:
1.518 + guard_attrs = [attr for _attrtype, object_type, attr in
1.519 + self._identify_reference_attribute(attrname, guard_class_types, guard_instance_types, guard_module_types)]
1.520 + else:
1.521 + guard_attrs = None
1.522 +
1.523 + self.reference_all_attrs[location] = all_accessed_attrs
1.524 + self.reference_all_attrtypes[location] = all_accessed_attrtypes
1.525 +
1.526 + # Suitably guarded accesses, where the nature of the
1.527 + # accessor can be guaranteed, do not require the attribute
1.528 + # involved to be validated. Otherwise, for unguarded
1.529 + # accesses, access-level tests are required.
1.530 +
1.531 + if not constrained:
1.532 +
1.533 + # Provide informational test types.
1.534 +
1.535 + if guarded and all_accessed_attrs.issubset(guard_attrs):
1.536 + if len(all_accessor_types) == 1:
1.537 + self.reference_test_types[location] = self.test_for_types("guarded-specific", all_accessor_types)
1.538 + elif self.is_single_class_type(all_accessor_types):
1.539 + self.reference_test_types[location] = "guarded-specific-object"
1.540 + elif len(all_accessor_general_types) == 1:
1.541 + self.reference_test_types[location] = self.test_for_types("guarded-common", all_accessor_general_types)
1.542 + elif self.is_single_class_type(all_accessor_general_types):
1.543 + self.reference_test_types[location] = "guarded-common-object"
1.544 +
1.545 + # Provide active test types.
1.546 +
1.547 + else:
1.548 + # Record the need to test the type of anonymous and
1.549 + # unconstrained accessors.
1.550 +
1.551 + if len(all_providers) == 1:
1.552 + provider = list(all_providers)[0]
1.553 + if provider != '__builtins__.object':
1.554 + all_accessor_kinds = set(self.get_kinds(all_accessor_types))
1.555 + if len(all_accessor_kinds) == 1:
1.556 + test_type = self.test_for_kinds("specific", all_accessor_kinds)
1.557 + else:
1.558 + test_type = "specific-object"
1.559 + self.reference_test_types[location] = test_type
1.560 + self.reference_test_accessor_types[location] = provider
1.561 +
1.562 + elif len(all_general_providers) == 1:
1.563 + provider = list(all_general_providers)[0]
1.564 + if provider != '__builtins__.object':
1.565 + all_accessor_kinds = set(self.get_kinds(all_accessor_general_types))
1.566 + if len(all_accessor_kinds) == 1:
1.567 + test_type = self.test_for_kinds("common", all_accessor_kinds)
1.568 + else:
1.569 + test_type = "common-object"
1.570 + self.reference_test_types[location] = test_type
1.571 + self.reference_test_accessor_types[location] = provider
1.572 +
1.573 + # Record the need to test the identity of the attribute.
1.574 +
1.575 + else:
1.576 + self.reference_test_types[location] = "validate"
1.577 +
1.578 + def get_referenced_attrs(self, location):
1.579 +
1.580 + """
1.581 + Return attributes referenced at the given access 'location' by the given
1.582 + 'attrname' as a list of (attribute type, attribute set) tuples.
1.583 + """
1.584 +
1.585 + ca = self.reference_class_attrs[location]
1.586 + ia = self.reference_instance_attrs[location]
1.587 + ma = self.reference_module_attrs[location]
1.588 +
1.589 + l = []
1.590 + for attrtype, attrs in (("<class>", ca), ("<instance>", ia), ("<module>", ma)):
1.591 + if attrs:
1.592 + l.append((attrtype, attrs))
1.593 + return l
1.594 +
1.595 + # Test generation methods.
1.596 +
1.597 + def get_kinds(self, all_types):
1.598 +
1.599 + """
1.600 + Return object kind details for 'all_types', being a collection of
1.601 + references for program types.
1.602 + """
1.603 +
1.604 + return map(lambda ref: ref.get_kind(), all_types)
1.605 +
1.606 + def test_for_types(self, prefix, all_types):
1.607 +
1.608 + """
1.609 + Return identifiers describing test conditions incorporating the given
1.610 + 'prefix' and involving 'all_types', being a collection of references to
1.611 + program types.
1.612 + """
1.613 +
1.614 + return self.test_for_kind(prefix, first(all_types).get_kind())
1.615 +
1.616 + def test_for_kinds(self, prefix, all_kinds):
1.617 +
1.618 + """
1.619 + Return identifiers describing test conditions incorporating the given
1.620 + 'prefix' and involving 'all_kinds', being a collection of object kinds.
1.621 + """
1.622 +
1.623 + return self.test_for_kind(prefix, first(all_kinds))
1.624 +
1.625 + def test_for_kind(self, prefix, kind):
1.626 +
1.627 + "Return a test condition identifier featuring 'prefix' and 'kind'."
1.628 +
1.629 + return "%s-%s" % (prefix, kind == "<instance>" and "instance" or "type")
1.630 +
1.631 + # Type handling methods.
1.632 +
1.633 + def is_single_class_type(self, all_types):
1.634 +
1.635 + """
1.636 + Return whether 'all_types' is a mixture of class and instance kinds for
1.637 + a single class type.
1.638 + """
1.639 +
1.640 + kinds = set()
1.641 + types = set()
1.642 +
1.643 + for type in all_types:
1.644 + kinds.add(type.get_kind())
1.645 + types.add(type.get_origin())
1.646 +
1.647 + return len(types) == 1 and kinds == set(["<class>", "<instance>"])
1.648 +
1.649 + def get_types_for_reference(self, ref):
1.650 +
1.651 + "Return class, instance-only and module types for 'ref'."
1.652 +
1.653 + class_types = ref.has_kind("<class>") and [ref.get_origin()] or []
1.654 + instance_types = []
1.655 + module_types = ref.has_kind("<module>") and [ref.get_origin()] or []
1.656 + return class_types, instance_types, module_types
1.657 +
1.658 + def combine_types(self, class_types, instance_types, module_types):
1.659 +
1.660 + """
1.661 + Combine 'class_types', 'instance_types', 'module_types' into a single
1.662 + list of references.
1.663 + """
1.664 +
1.665 + all_types = []
1.666 + for kind, l in [("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types)]:
1.667 + for t in l:
1.668 + all_types.append(Reference(kind, t))
1.669 + return all_types
1.670 +
1.671 + def separate_types(self, refs):
1.672 +
1.673 + """
1.674 + Separate 'refs' into type-specific lists, returning a tuple containing
1.675 + lists of class types, instance types, module types, function types and
1.676 + unknown "var" types.
1.677 + """
1.678 +
1.679 + class_types = []
1.680 + instance_types = []
1.681 + module_types = []
1.682 + function_types = []
1.683 + var_types = []
1.684 +
1.685 + for kind, l in [
1.686 + ("<class>", class_types), ("<instance>", instance_types), ("<module>", module_types),
1.687 + ("<function>", function_types), ("<var>", var_types)
1.688 + ]:
1.689 +
1.690 + for ref in refs:
1.691 + if ref.get_kind() == kind:
1.692 + l.append(ref.get_origin())
1.693 +
1.694 + return class_types, instance_types, module_types, function_types, var_types
1.695 +
1.696 + # Initialisation methods.
1.697 +
1.698 + def init_descendants(self):
1.699 +
1.700 + "Identify descendants of each class."
1.701 +
1.702 + for name in self.importer.classes.keys():
1.703 + self.get_descendants_for_class(name)
1.704 +
1.705 + def get_descendants_for_class(self, name):
1.706 +
1.707 + """
1.708 + Use subclass information to deduce the descendants for the class of the
1.709 + given 'name'.
1.710 + """
1.711 +
1.712 + if not self.descendants.has_key(name):
1.713 + descendants = set()
1.714 +
1.715 + for subclass in self.importer.subclasses[name]:
1.716 + descendants.update(self.get_descendants_for_class(subclass))
1.717 + descendants.add(subclass)
1.718 +
1.719 + self.descendants[name] = descendants
1.720 +
1.721 + return self.descendants[name]
1.722 +
1.723 + def init_special_attributes(self):
1.724 +
1.725 + "Add special attributes to the classes for inheritance-related tests."
1.726 +
1.727 + all_class_attrs = self.importer.all_class_attrs
1.728 +
1.729 + for name, descendants in self.descendants.items():
1.730 + for descendant in descendants:
1.731 + all_class_attrs[descendant]["#%s" % name] = name
1.732 +
1.733 + for name in all_class_attrs.keys():
1.734 + all_class_attrs[name]["#%s" % name] = name
1.735 +
1.736 + def init_usage_index(self):
1.737 +
1.738 + """
1.739 + Create indexes for module and function attribute usage and for anonymous
1.740 + accesses.
1.741 + """
1.742 +
1.743 + for module in self.importer.get_modules():
1.744 + for path, assignments in module.attr_usage.items():
1.745 + self.add_usage(assignments, path)
1.746 +
1.747 + for location, all_attrnames in self.importer.all_attr_accesses.items():
1.748 + for attrnames in all_attrnames:
1.749 + attrname = get_attrnames(attrnames)[-1]
1.750 + access_location = (location, None, attrnames, 0)
1.751 + self.add_usage_term(access_location, [attrname])
1.752 +
1.753 + def add_usage(self, assignments, path):
1.754 +
1.755 + """
1.756 + Collect usage from the given 'assignments', adding 'path' details to
1.757 + each record if specified. Add the usage to an index mapping to location
1.758 + information, as well as to an index mapping locations to usages.
1.759 + """
1.760 +
1.761 + for name, versions in assignments.items():
1.762 + for i, usages in enumerate(versions):
1.763 + location = (path, name, None, i)
1.764 +
1.765 + for attrnames in usages:
1.766 + self.add_usage_term(location, attrnames)
1.767 +
1.768 + def add_usage_term(self, location, attrnames):
1.769 +
1.770 + """
1.771 + For 'location' and using 'attrnames' as a description of usage, record
1.772 + in the usage index a mapping from the usage to 'location', and record in
1.773 + the location index a mapping from 'location' to the usage.
1.774 + """
1.775 +
1.776 + key = make_key(attrnames)
1.777 +
1.778 + init_item(self.location_index, location, set)
1.779 + self.location_index[location].add(key)
1.780 +
1.781 + def init_accessors(self):
1.782 +
1.783 + "Create indexes for module and function accessor information."
1.784 +
1.785 + for module in self.importer.get_modules():
1.786 + for path, all_accesses in module.attr_accessors.items():
1.787 + self.add_accessors(all_accesses, path)
1.788 +
1.789 + def add_accessors(self, all_accesses, path):
1.790 +
1.791 + """
1.792 + For attribute accesses described by the mapping of 'all_accesses' from
1.793 + name details to accessor details, record the locations of the accessors
1.794 + for each access.
1.795 + """
1.796 +
1.797 + # Get details for each access combining the given name and attribute.
1.798 +
1.799 + for (name, attrnames), accesses in all_accesses.items():
1.800 +
1.801 + # Obtain the usage details using the access information.
1.802 +
1.803 + for access_number, versions in enumerate(accesses):
1.804 + access_location = (path, name, attrnames, access_number)
1.805 + locations = []
1.806 +
1.807 + for version in versions:
1.808 + location = (path, name, None, version)
1.809 + locations.append(location)
1.810 +
1.811 + self.access_index[access_location] = locations
1.812 +
1.813 + def get_accessors_for_access(self, access_location):
1.814 +
1.815 + "Find a definition providing accessor details, if necessary."
1.816 +
1.817 + try:
1.818 + return self.access_index[access_location]
1.819 + except KeyError:
1.820 + return [access_location]
1.821 +
1.822 + def init_accesses(self):
1.823 +
1.824 + """
1.825 + Initialise collections for accesses involving assignments.
1.826 + """
1.827 +
1.828 + # For each scope, obtain access details.
1.829 +
1.830 + for path, all_accesses in self.importer.all_attr_access_modifiers.items():
1.831 +
1.832 + # For each combination of name and attribute names, obtain
1.833 + # applicable modifiers.
1.834 +
1.835 + for (name, attrnames), modifiers in all_accesses.items():
1.836 +
1.837 + # For each access, determine the name versions affected by
1.838 + # assignments.
1.839 +
1.840 + for access_number, assignment in enumerate(modifiers):
1.841 + if name:
1.842 + access_location = (path, name, attrnames, access_number)
1.843 + else:
1.844 + access_location = (path, None, attrnames, 0)
1.845 +
1.846 + # Associate assignments with usage.
1.847 +
1.848 + accessor_locations = self.get_accessors_for_access(access_location)
1.849 +
1.850 + for location in accessor_locations:
1.851 + for usage in self.location_index[location]:
1.852 + key = make_key(usage)
1.853 +
1.854 + if assignment:
1.855 + init_item(self.assigned_attrs, key, set)
1.856 + self.assigned_attrs[key].add((path, name, attrnames))
1.857 +
1.858 + def init_aliases(self):
1.859 +
1.860 + "Expand aliases so that alias-based accesses can be resolved."
1.861 +
1.862 + # Get aliased names with details of their accesses.
1.863 +
1.864 + for name_path, all_aliases in self.importer.all_aliased_names.items():
1.865 + path, name = name_path.rsplit(".", 1)
1.866 +
1.867 + # For each version of the name, obtain the access location.
1.868 +
1.869 + for version, (original_name, attrnames, access_number) in all_aliases.items():
1.870 + accessor_location = (path, name, None, version)
1.871 + access_location = (path, original_name, attrnames, access_number)
1.872 + init_item(self.alias_index, accessor_location, list)
1.873 + self.alias_index[accessor_location].append(access_location)
1.874 +
1.875 + # Get aliases in terms of non-aliases and accesses.
1.876 +
1.877 + for accessor_location, access_locations in self.alias_index.items():
1.878 + self.update_aliases(accessor_location, access_locations)
1.879 +
1.880 + def update_aliases(self, accessor_location, access_locations, visited=None):
1.881 +
1.882 + """
1.883 + Update the given 'accessor_location' defining an alias, update
1.884 + 'access_locations' to refer to non-aliases, following name references
1.885 + via the access index.
1.886 +
1.887 + If 'visited' is specified, it contains a set of accessor locations (and
1.888 + thus keys to the alias index) that are currently being defined.
1.889 + """
1.890 +
1.891 + if visited is None:
1.892 + visited = set()
1.893 +
1.894 + updated_locations = set()
1.895 +
1.896 + for access_location in access_locations:
1.897 + (path, original_name, attrnames, access_number) = access_location
1.898 +
1.899 + # Where an alias refers to a name access, obtain the original name
1.900 + # version details.
1.901 +
1.902 + if attrnames is None:
1.903 +
1.904 + # For each name version, attempt to determine any accesses that
1.905 + # initialise the name.
1.906 +
1.907 + for name_accessor_location in self.access_index[access_location]:
1.908 +
1.909 + # Already-visited aliases do not contribute details.
1.910 +
1.911 + if name_accessor_location in visited:
1.912 + continue
1.913 +
1.914 + visited.add(name_accessor_location)
1.915 +
1.916 + name_access_locations = self.alias_index.get(name_accessor_location)
1.917 + if name_access_locations:
1.918 + updated_locations.update(self.update_aliases(name_accessor_location, name_access_locations, visited))
1.919 + else:
1.920 + updated_locations.add(name_accessor_location)
1.921 +
1.922 + # Otherwise, record the access details.
1.923 +
1.924 + else:
1.925 + updated_locations.add(access_location)
1.926 +
1.927 + self.alias_index[accessor_location] = updated_locations
1.928 + return updated_locations
1.929 +
1.930 + # Attribute mutation for types.
1.931 +
1.932 + def modify_mutated_attributes(self):
1.933 +
1.934 + "Identify known, mutated attributes and change their state."
1.935 +
1.936 + # Usage-based accesses.
1.937 +
1.938 + for usage, all_attrnames in self.assigned_attrs.items():
1.939 + if not usage:
1.940 + continue
1.941 +
1.942 + for path, name, attrnames in all_attrnames:
1.943 + class_types = self.get_class_types_for_usage(usage)
1.944 + only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types)
1.945 + module_types = self.get_module_types_for_usage(usage)
1.946 +
1.947 + # Detect self usage within methods in order to narrow the scope
1.948 + # of the mutation.
1.949 +
1.950 + t = name == "self" and self.constrain_self_reference(path, class_types, only_instance_types)
1.951 + if t:
1.952 + class_types, only_instance_types, module_types, constrained = t
1.953 + objects = set(class_types).union(only_instance_types).union(module_types)
1.954 +
1.955 + self.mutate_attribute(objects, attrnames)
1.956 +
1.957 + def mutate_attribute(self, objects, attrnames):
1.958 +
1.959 + "Mutate static 'objects' with the given 'attrnames'."
1.960 +
1.961 + for name in objects:
1.962 + attr = "%s.%s" % (name, attrnames)
1.963 + value = self.importer.get_object(attr)
1.964 +
1.965 + # If the value is None, the attribute is
1.966 + # inherited and need not be set explicitly on
1.967 + # the class concerned.
1.968 +
1.969 + if value:
1.970 + self.modified_attributes[attr] = value
1.971 + self.importer.set_object(attr, value.as_var())
1.972 +
1.973 + # Simplification of types.
1.974 +
1.975 + def get_most_general_types(self, class_types):
1.976 +
1.977 + "Return the most general types for the given 'class_types'."
1.978 +
1.979 + class_types = set(class_types)
1.980 + to_remove = set()
1.981 +
1.982 + for class_type in class_types:
1.983 + for base in self.importer.classes[class_type]:
1.984 + base = base.get_origin()
1.985 + descendants = self.descendants[base]
1.986 + if base in class_types and descendants.issubset(class_types):
1.987 + to_remove.update(descendants)
1.988 +
1.989 + class_types.difference_update(to_remove)
1.990 + return class_types
1.991 +
1.992 + def get_most_general_module_types(self, module_types):
1.993 +
1.994 + "Return the most general type for the given 'module_types'."
1.995 +
1.996 + # Where all modules are provided, an object would provide the same
1.997 + # attributes.
1.998 +
1.999 + if len(module_types) == len(self.importer.modules):
1.1000 + return ["__builtins__.object"]
1.1001 + else:
1.1002 + return module_types
1.1003 +
1.1004 + # Type deduction for usage.
1.1005 +
1.1006 + def get_types_for_usage(self, attrnames, objects):
1.1007 +
1.1008 + """
1.1009 + Identify the types that can support the given 'attrnames', using the
1.1010 + given 'objects' as the catalogue of type details.
1.1011 + """
1.1012 +
1.1013 + types = []
1.1014 + for name, _attrnames in objects.items():
1.1015 + if set(attrnames).issubset(_attrnames):
1.1016 + types.append(name)
1.1017 + return types
1.1018 +
1.1019 + # More efficient usage-to-type indexing and retrieval.
1.1020 +
1.1021 + def init_attr_type_indexes(self):
1.1022 +
1.1023 + "Identify the types that can support each attribute name."
1.1024 +
1.1025 + self._init_attr_type_index(self.attr_class_types, self.importer.all_class_attrs)
1.1026 + self._init_attr_type_index(self.attr_instance_types, self.importer.all_combined_attrs)
1.1027 + self._init_attr_type_index(self.attr_module_types, self.importer.all_module_attrs)
1.1028 +
1.1029 + def _init_attr_type_index(self, attr_types, attrs):
1.1030 +
1.1031 + """
1.1032 + Initialise the 'attr_types' attribute-to-types mapping using the given
1.1033 + 'attrs' type-to-attributes mapping.
1.1034 + """
1.1035 +
1.1036 + for name, attrnames in attrs.items():
1.1037 + for attrname in attrnames:
1.1038 + init_item(attr_types, attrname, set)
1.1039 + attr_types[attrname].add(name)
1.1040 +
1.1041 + def get_class_types_for_usage(self, attrnames):
1.1042 +
1.1043 + "Return names of classes supporting the given 'attrnames'."
1.1044 +
1.1045 + return self._get_types_for_usage(attrnames, self.attr_class_types, self.importer.all_class_attrs)
1.1046 +
1.1047 + def get_instance_types_for_usage(self, attrnames):
1.1048 +
1.1049 + """
1.1050 + Return names of classes whose instances support the given 'attrnames'
1.1051 + (as either class or instance attributes).
1.1052 + """
1.1053 +
1.1054 + return self._get_types_for_usage(attrnames, self.attr_instance_types, self.importer.all_combined_attrs)
1.1055 +
1.1056 + def get_module_types_for_usage(self, attrnames):
1.1057 +
1.1058 + "Return names of modules supporting the given 'attrnames'."
1.1059 +
1.1060 + return self._get_types_for_usage(attrnames, self.attr_module_types, self.importer.all_module_attrs)
1.1061 +
1.1062 + def _get_types_for_usage(self, attrnames, attr_types, attrs):
1.1063 +
1.1064 + """
1.1065 + For the given 'attrnames' representing attribute usage, return types
1.1066 + recorded in the 'attr_types' attribute-to-types mapping that support
1.1067 + such usage, with the given 'attrs' type-to-attributes mapping used to
1.1068 + quickly assess whether a type supports all of the stated attributes.
1.1069 + """
1.1070 +
1.1071 + # Where no attributes are used, any type would be acceptable.
1.1072 +
1.1073 + if not attrnames:
1.1074 + return attrs.keys()
1.1075 +
1.1076 + types = []
1.1077 +
1.1078 + # Obtain types supporting the first attribute name...
1.1079 +
1.1080 + for name in attr_types.get(attrnames[0]) or []:
1.1081 +
1.1082 + # Record types that support all of the other attributes as well.
1.1083 +
1.1084 + _attrnames = attrs[name]
1.1085 + if set(attrnames).issubset(_attrnames):
1.1086 + types.append(name)
1.1087 +
1.1088 + return types
1.1089 +
1.1090 + # Reference identification.
1.1091 +
1.1092 + def identify_references(self):
1.1093 +
1.1094 + "Identify references using usage and name reference information."
1.1095 +
1.1096 + # Names with associated attribute usage.
1.1097 +
1.1098 + for location, usages in self.location_index.items():
1.1099 +
1.1100 + # Obtain attribute usage associated with a name, deducing the nature
1.1101 + # of the name. Obtain types only for branches involving attribute
1.1102 + # usage. (In the absence of usage, any type could be involved, but
1.1103 + # then no accesses exist to require knowledge of the type.)
1.1104 +
1.1105 + have_usage = False
1.1106 + have_no_usage_branch = False
1.1107 +
1.1108 + for usage in usages:
1.1109 + if not usage:
1.1110 + have_no_usage_branch = True
1.1111 + continue
1.1112 + elif not have_usage:
1.1113 + self.init_definition_details(location)
1.1114 + have_usage = True
1.1115 + self.record_types_for_usage(location, usage)
1.1116 +
1.1117 + # Where some usage occurs, but where branches without usage also
1.1118 + # occur, record the types for those branches anyway.
1.1119 +
1.1120 + if have_usage and have_no_usage_branch:
1.1121 + self.init_definition_details(location)
1.1122 + self.record_types_for_usage(location, None)
1.1123 +
1.1124 + # Specific name-based attribute accesses.
1.1125 +
1.1126 + alias_accesses = set()
1.1127 +
1.1128 + for access_location, accessor_locations in self.access_index.items():
1.1129 + self.record_types_for_access(access_location, accessor_locations, alias_accesses)
1.1130 +
1.1131 + # Aliased name definitions. All aliases with usage will have been
1.1132 + # defined, but they may be refined according to referenced accesses.
1.1133 +
1.1134 + for accessor_location in self.alias_index.keys():
1.1135 + self.record_types_for_alias(accessor_location)
1.1136 +
1.1137 + # Update accesses employing aliases.
1.1138 +
1.1139 + for access_location in alias_accesses:
1.1140 + self.record_types_for_access(access_location, self.access_index[access_location])
1.1141 +
1.1142 + # Anonymous references with attribute chains.
1.1143 +
1.1144 + for location, accesses in self.importer.all_attr_accesses.items():
1.1145 +
1.1146 + # Get distinct attribute names.
1.1147 +
1.1148 + all_attrnames = set()
1.1149 +
1.1150 + for attrnames in accesses:
1.1151 + all_attrnames.update(get_attrnames(attrnames))
1.1152 +
1.1153 + # Get attribute and accessor details for each attribute name.
1.1154 +
1.1155 + for attrname in all_attrnames:
1.1156 + access_location = (location, None, attrname, 0)
1.1157 + self.record_types_for_attribute(access_location, attrname)
1.1158 +
1.1159 + # References via constant/identified objects.
1.1160 +
1.1161 + for location, name_accesses in self.importer.all_const_accesses.items():
1.1162 +
1.1163 + # A mapping from the original name and attributes to resolved access
1.1164 + # details.
1.1165 +
1.1166 + for original_access, access in name_accesses.items():
1.1167 + original_name, original_attrnames = original_access
1.1168 + objpath, ref, attrnames = access
1.1169 +
1.1170 + # Build an accessor combining the name and attribute names used.
1.1171 +
1.1172 + original_accessor = tuple([original_name] + original_attrnames.split("."))
1.1173 +
1.1174 + # Direct accesses to attributes.
1.1175 +
1.1176 + if not attrnames:
1.1177 +
1.1178 + # Build a descriptive location based on the original
1.1179 + # details, exposing the final attribute name.
1.1180 +
1.1181 + oa, attrname = original_accessor[:-1], original_accessor[-1]
1.1182 + oa = ".".join(oa)
1.1183 +
1.1184 + access_location = (location, oa, attrname, 0)
1.1185 + accessor_location = (location, oa, None, 0)
1.1186 + self.access_index[access_location] = [accessor_location]
1.1187 +
1.1188 + self.init_access_details(access_location)
1.1189 + self.init_definition_details(accessor_location)
1.1190 +
1.1191 + # Obtain a reference for the accessor in order to properly
1.1192 + # determine its type.
1.1193 +
1.1194 + if ref.get_kind() != "<instance>":
1.1195 + objpath = ref.get_origin()
1.1196 +
1.1197 + objpath = objpath.rsplit(".", 1)[0]
1.1198 +
1.1199 + # Where the object name conflicts with the module
1.1200 + # providing it, obtain the module details.
1.1201 +
1.1202 + if objpath in self.importer.modules:
1.1203 + accessor = Reference("<module>", objpath)
1.1204 + else:
1.1205 + accessor = self.importer.get_object(objpath)
1.1206 +
1.1207 + self.referenced_attrs[access_location] = [(accessor.get_kind(), accessor.get_origin(), ref)]
1.1208 + self.access_constrained.add(access_location)
1.1209 +
1.1210 + class_types, instance_types, module_types = self.get_types_for_reference(accessor)
1.1211 + self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True)
1.1212 + continue
1.1213 +
1.1214 + # Build a descriptive location based on the original
1.1215 + # details, employing the first remaining attribute name.
1.1216 +
1.1217 + l = get_attrnames(attrnames)
1.1218 + attrname = l[0]
1.1219 +
1.1220 + oa = original_accessor[:-len(l)]
1.1221 + oa = ".".join(oa)
1.1222 +
1.1223 + access_location = (location, oa, attrnames, 0)
1.1224 + accessor_location = (location, oa, None, 0)
1.1225 + self.access_index[access_location] = [accessor_location]
1.1226 +
1.1227 + self.init_access_details(access_location)
1.1228 + self.init_definition_details(accessor_location)
1.1229 +
1.1230 + class_types, instance_types, module_types = self.get_types_for_reference(ref)
1.1231 +
1.1232 + self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, True)
1.1233 + self.record_reference_types(accessor_location, class_types, instance_types, module_types, True, True)
1.1234 +
1.1235 + def constrain_types(self, path, class_types, instance_types, module_types):
1.1236 +
1.1237 + """
1.1238 + Using the given 'path' to an object, constrain the given 'class_types',
1.1239 + 'instance_types' and 'module_types'.
1.1240 +
1.1241 + Return the class, instance, module types plus whether the types are
1.1242 + constrained to a specific kind of type.
1.1243 + """
1.1244 +
1.1245 + ref = self.importer.identify(path)
1.1246 + if ref:
1.1247 +
1.1248 + # Constrain usage suggestions using the identified object.
1.1249 +
1.1250 + if ref.has_kind("<class>"):
1.1251 + return (
1.1252 + set(class_types).intersection([ref.get_origin()]), [], [], True
1.1253 + )
1.1254 + elif ref.has_kind("<module>"):
1.1255 + return (
1.1256 + [], [], set(module_types).intersection([ref.get_origin()]), True
1.1257 + )
1.1258 +
1.1259 + return class_types, instance_types, module_types, False
1.1260 +
1.1261 + def get_target_types(self, location, usage):
1.1262 +
1.1263 + """
1.1264 + Return the class, instance and module types constrained for the name at
1.1265 + the given 'location' exhibiting the given 'usage'. Whether the types
1.1266 + have been constrained using contextual information is also indicated,
1.1267 + plus whether the types have been constrained to a specific kind of type.
1.1268 + """
1.1269 +
1.1270 + unit_path, name, attrnames, version = location
1.1271 +
1.1272 + # Detect any initialised name for the location.
1.1273 +
1.1274 + if name:
1.1275 + ref = self.get_initialised_name(location)
1.1276 + if ref:
1.1277 + (class_types, only_instance_types, module_types,
1.1278 + _function_types, _var_types) = self.separate_types([ref])
1.1279 + return class_types, only_instance_types, module_types, True, False
1.1280 +
1.1281 + # Retrieve the recorded types for the usage.
1.1282 +
1.1283 + class_types = self.get_class_types_for_usage(usage)
1.1284 + only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types)
1.1285 + module_types = self.get_module_types_for_usage(usage)
1.1286 +
1.1287 + # Merge usage deductions with observations to obtain reference types
1.1288 + # for names involved with attribute accesses.
1.1289 +
1.1290 + if not name:
1.1291 + return class_types, only_instance_types, module_types, False, False
1.1292 +
1.1293 + # Obtain references to known objects.
1.1294 +
1.1295 + path = self.get_name_path(unit_path, name)
1.1296 +
1.1297 + class_types, only_instance_types, module_types, constrained_specific = \
1.1298 + self.constrain_types(path, class_types, only_instance_types, module_types)
1.1299 +
1.1300 + if constrained_specific:
1.1301 + return class_types, only_instance_types, module_types, constrained_specific, constrained_specific
1.1302 +
1.1303 + # Constrain "self" references.
1.1304 +
1.1305 + if name == "self":
1.1306 + t = self.constrain_self_reference(unit_path, class_types, only_instance_types)
1.1307 + if t:
1.1308 + class_types, only_instance_types, module_types, constrained = t
1.1309 + return class_types, only_instance_types, module_types, constrained, False
1.1310 +
1.1311 + return class_types, only_instance_types, module_types, False, False
1.1312 +
1.1313 + def constrain_self_reference(self, unit_path, class_types, only_instance_types):
1.1314 +
1.1315 + """
1.1316 + Where the name "self" appears in a method, attempt to constrain the
1.1317 + classes involved.
1.1318 +
1.1319 + Return the class, instance, module types plus whether the types are
1.1320 + constrained.
1.1321 + """
1.1322 +
1.1323 + class_name = self.in_method(unit_path)
1.1324 +
1.1325 + if not class_name:
1.1326 + return None
1.1327 +
1.1328 + classes = set([class_name])
1.1329 + classes.update(self.get_descendants_for_class(class_name))
1.1330 +
1.1331 + # Note that only instances will be expected for these references but
1.1332 + # either classes or instances may provide the attributes.
1.1333 +
1.1334 + return (
1.1335 + set(class_types).intersection(classes),
1.1336 + set(only_instance_types).intersection(classes),
1.1337 + [], True
1.1338 + )
1.1339 +
1.1340 + def in_method(self, path):
1.1341 +
1.1342 + "Return whether 'path' refers to a method."
1.1343 +
1.1344 + class_name, method_name = path.rsplit(".", 1)
1.1345 + return self.importer.classes.has_key(class_name) and class_name
1.1346 +
1.1347 + def init_reference_details(self, location):
1.1348 +
1.1349 + "Initialise reference-related details for 'location'."
1.1350 +
1.1351 + self.init_definition_details(location)
1.1352 + self.init_access_details(location)
1.1353 +
1.1354 + def init_definition_details(self, location):
1.1355 +
1.1356 + "Initialise name definition details for 'location'."
1.1357 +
1.1358 + self.accessor_class_types[location] = set()
1.1359 + self.accessor_instance_types[location] = set()
1.1360 + self.accessor_module_types[location] = set()
1.1361 + self.provider_class_types[location] = set()
1.1362 + self.provider_instance_types[location] = set()
1.1363 + self.provider_module_types[location] = set()
1.1364 +
1.1365 + def init_access_details(self, location):
1.1366 +
1.1367 + "Initialise access details at 'location'."
1.1368 +
1.1369 + self.referenced_attrs[location] = {}
1.1370 +
1.1371 + def record_types_for_access(self, access_location, accessor_locations, alias_accesses=None):
1.1372 +
1.1373 + """
1.1374 + Define types for the 'access_location' associated with the given
1.1375 + 'accessor_locations'.
1.1376 + """
1.1377 +
1.1378 + path, name, attrnames, version = access_location
1.1379 + if not attrnames:
1.1380 + return
1.1381 +
1.1382 + attrname = get_attrnames(attrnames)[0]
1.1383 +
1.1384 + # Collect all suggested types for the accessors. Accesses may
1.1385 + # require accessors from of a subset of the complete set of types.
1.1386 +
1.1387 + class_types = set()
1.1388 + module_types = set()
1.1389 + instance_types = set()
1.1390 +
1.1391 + constrained = True
1.1392 +
1.1393 + for location in accessor_locations:
1.1394 +
1.1395 + # Remember accesses employing aliases.
1.1396 +
1.1397 + if alias_accesses is not None and self.alias_index.has_key(location):
1.1398 + alias_accesses.add(access_location)
1.1399 +
1.1400 + # Use the type information deduced for names from above.
1.1401 +
1.1402 + if self.accessor_class_types.has_key(location):
1.1403 + class_types.update(self.accessor_class_types[location])
1.1404 + module_types.update(self.accessor_module_types[location])
1.1405 + instance_types.update(self.accessor_instance_types[location])
1.1406 +
1.1407 + # Where accesses are associated with assignments but where no
1.1408 + # attribute usage observations have caused such an association,
1.1409 + # the attribute name is considered by itself.
1.1410 +
1.1411 + else:
1.1412 + self.init_definition_details(location)
1.1413 + self.record_types_for_usage(location, [attrname])
1.1414 +
1.1415 + constrained = location in self.reference_constrained and constrained
1.1416 +
1.1417 + self.init_access_details(access_location)
1.1418 + self.identify_reference_attributes(access_location, attrname, class_types, instance_types, module_types, constrained)
1.1419 +
1.1420 + def record_types_for_usage(self, accessor_location, usage):
1.1421 +
1.1422 + """
1.1423 + Record types for the given 'accessor_location' according to the given
1.1424 + 'usage' observations which may be None to indicate an absence of usage.
1.1425 + """
1.1426 +
1.1427 + (class_types,
1.1428 + instance_types,
1.1429 + module_types,
1.1430 + constrained,
1.1431 + constrained_specific) = self.get_target_types(accessor_location, usage)
1.1432 +
1.1433 + self.record_reference_types(accessor_location, class_types, instance_types, module_types, constrained, constrained_specific)
1.1434 +
1.1435 + def record_types_for_attribute(self, access_location, attrname):
1.1436 +
1.1437 + """
1.1438 + Record types for the 'access_location' employing only the given
1.1439 + 'attrname' for type deduction.
1.1440 + """
1.1441 +
1.1442 + usage = [attrname]
1.1443 +
1.1444 + class_types = self.get_class_types_for_usage(usage)
1.1445 + only_instance_types = set(self.get_instance_types_for_usage(usage)).difference(class_types)
1.1446 + module_types = self.get_module_types_for_usage(usage)
1.1447 +
1.1448 + self.init_reference_details(access_location)
1.1449 +
1.1450 + self.identify_reference_attributes(access_location, attrname, class_types, only_instance_types, module_types, False)
1.1451 + self.record_reference_types(access_location, class_types, only_instance_types, module_types, False)
1.1452 +
1.1453 + def record_types_for_alias(self, accessor_location):
1.1454 +
1.1455 + """
1.1456 + Define types for the 'accessor_location' not having associated usage.
1.1457 + """
1.1458 +
1.1459 + have_access = self.provider_class_types.has_key(accessor_location)
1.1460 +
1.1461 + # With an access, attempt to narrow the existing selection of provider
1.1462 + # types.
1.1463 +
1.1464 + if have_access:
1.1465 + provider_class_types = self.provider_class_types[accessor_location]
1.1466 + provider_instance_types = self.provider_instance_types[accessor_location]
1.1467 + provider_module_types = self.provider_module_types[accessor_location]
1.1468 +
1.1469 + # Find details for any corresponding access.
1.1470 +
1.1471 + all_class_types = set()
1.1472 + all_instance_types = set()
1.1473 + all_module_types = set()
1.1474 +
1.1475 + for access_location in self.alias_index[accessor_location]:
1.1476 + location, name, attrnames, access_number = access_location
1.1477 +
1.1478 + # Alias references an attribute access.
1.1479 +
1.1480 + if attrnames:
1.1481 +
1.1482 + # Obtain attribute references for the access.
1.1483 +
1.1484 + attrs = [attr for _attrtype, object_type, attr in self.referenced_attrs[access_location]]
1.1485 +
1.1486 + # Separate the different attribute types.
1.1487 +
1.1488 + (class_types, instance_types, module_types,
1.1489 + function_types, var_types) = self.separate_types(attrs)
1.1490 +
1.1491 + # Where non-accessor types are found, do not attempt to refine
1.1492 + # the defined accessor types.
1.1493 +
1.1494 + if function_types or var_types:
1.1495 + return
1.1496 +
1.1497 + class_types = set(provider_class_types).intersection(class_types)
1.1498 + instance_types = set(provider_instance_types).intersection(instance_types)
1.1499 + module_types = set(provider_module_types).intersection(module_types)
1.1500 +
1.1501 + # Alias references a name, not an access.
1.1502 +
1.1503 + else:
1.1504 + # Attempt to refine the types using initialised names.
1.1505 +
1.1506 + attr = self.get_initialised_name(access_location)
1.1507 + if attr:
1.1508 + (class_types, instance_types, module_types,
1.1509 + _function_types, _var_types) = self.separate_types([attr])
1.1510 +
1.1511 + # Where no further information is found, do not attempt to
1.1512 + # refine the defined accessor types.
1.1513 +
1.1514 + else:
1.1515 + return
1.1516 +
1.1517 + all_class_types.update(class_types)
1.1518 + all_instance_types.update(instance_types)
1.1519 + all_module_types.update(module_types)
1.1520 +
1.1521 + # Record refined type details for the alias as an accessor.
1.1522 +
1.1523 + self.init_definition_details(accessor_location)
1.1524 + self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False)
1.1525 +
1.1526 + # Without an access, attempt to identify references for the alias.
1.1527 +
1.1528 + else:
1.1529 + refs = set()
1.1530 +
1.1531 + for access_location in self.alias_index[accessor_location]:
1.1532 + location, name, attrnames, access_number = access_location
1.1533 +
1.1534 + # Alias references an attribute access.
1.1535 +
1.1536 + if attrnames:
1.1537 + attrs = [attr for attrtype, object_type, attr in self.referenced_attrs[access_location]]
1.1538 + refs.update(attrs)
1.1539 +
1.1540 + # Alias references a name, not an access.
1.1541 +
1.1542 + else:
1.1543 + attr = self.get_initialised_name(access_location)
1.1544 + attrs = attr and [attr] or []
1.1545 + if not attrs and self.provider_class_types.has_key(access_location):
1.1546 + class_types = self.provider_class_types[access_location]
1.1547 + instance_types = self.provider_instance_types[access_location]
1.1548 + module_types = self.provider_module_types[access_location]
1.1549 + attrs = self.combine_types(class_types, instance_types, module_types)
1.1550 + if attrs:
1.1551 + refs.update(attrs)
1.1552 +
1.1553 + # Record reference details for the alias separately from accessors.
1.1554 +
1.1555 + self.referenced_objects[accessor_location] = refs
1.1556 +
1.1557 + def get_initialised_name(self, access_location):
1.1558 +
1.1559 + """
1.1560 + Return references for any initialised names at 'access_location', or
1.1561 + None if no such references exist.
1.1562 + """
1.1563 +
1.1564 + location, name, attrnames, version = access_location
1.1565 + path = self.get_name_path(location, name)
1.1566 +
1.1567 + # Use initialiser information, if available.
1.1568 +
1.1569 + refs = self.importer.all_initialised_names.get(path)
1.1570 + if refs and refs.has_key(version):
1.1571 + return refs[version]
1.1572 + else:
1.1573 + return None
1.1574 +
1.1575 + def get_name_path(self, path, name):
1.1576 +
1.1577 + "Return a suitable qualified name from the given 'path' and 'name'."
1.1578 +
1.1579 + if "." in name:
1.1580 + return name
1.1581 + else:
1.1582 + return "%s.%s" % (path, name)
1.1583 +
1.1584 + def record_reference_types(self, location, class_types, instance_types,
1.1585 + module_types, constrained, constrained_specific=False):
1.1586 +
1.1587 + """
1.1588 + Associate attribute provider types with the given 'location', consisting
1.1589 + of the given 'class_types', 'instance_types' and 'module_types'.
1.1590 +
1.1591 + If 'constrained' is indicated, the constrained nature of the accessor is
1.1592 + recorded for the location.
1.1593 +
1.1594 + If 'constrained_specific' is indicated using a true value, instance types
1.1595 + will not be added to class types to permit access via instances at the
1.1596 + given location. This is only useful where a specific accessor is known
1.1597 + to be a class.
1.1598 +
1.1599 + Note that the specified types only indicate the provider types for
1.1600 + attributes, whereas the recorded accessor types indicate the possible
1.1601 + types of the actual objects used to access attributes.
1.1602 + """
1.1603 +
1.1604 + # Update the type details for the location.
1.1605 +
1.1606 + self.provider_class_types[location].update(class_types)
1.1607 + self.provider_instance_types[location].update(instance_types)
1.1608 + self.provider_module_types[location].update(module_types)
1.1609 +
1.1610 + # Class types support classes and instances as accessors.
1.1611 + # Instance-only and module types support only their own kinds as
1.1612 + # accessors.
1.1613 +
1.1614 + # However, the nature of accessors can be further determined.
1.1615 + # Any self variable may only refer to an instance.
1.1616 +
1.1617 + path, name, version, attrnames = location
1.1618 + if name != "self" or not self.in_method(path):
1.1619 + self.accessor_class_types[location].update(class_types)
1.1620 +
1.1621 + if not constrained_specific:
1.1622 + self.accessor_instance_types[location].update(class_types)
1.1623 +
1.1624 + self.accessor_instance_types[location].update(instance_types)
1.1625 +
1.1626 + if name != "self" or not self.in_method(path):
1.1627 + self.accessor_module_types[location].update(module_types)
1.1628 +
1.1629 + if constrained:
1.1630 + self.reference_constrained.add(location)
1.1631 +
1.1632 + def identify_reference_attributes(self, location, attrname, class_types, instance_types, module_types, constrained):
1.1633 +
1.1634 + """
1.1635 + Identify reference attributes, associating them with the given
1.1636 + 'location', identifying the given 'attrname', employing the given
1.1637 + 'class_types', 'instance_types' and 'module_types'.
1.1638 +
1.1639 + If 'constrained' is indicated, the constrained nature of the access is
1.1640 + recorded for the location.
1.1641 + """
1.1642 +
1.1643 + # Record the referenced objects.
1.1644 +
1.1645 + self.referenced_attrs[location] = \
1.1646 + self._identify_reference_attribute(attrname, class_types, instance_types, module_types)
1.1647 +
1.1648 + if constrained:
1.1649 + self.access_constrained.add(location)
1.1650 +
1.1651 + def _identify_reference_attribute(self, attrname, class_types, instance_types, module_types):
1.1652 +
1.1653 + """
1.1654 + Identify the reference attribute with the given 'attrname', employing
1.1655 + the given 'class_types', 'instance_types' and 'module_types'.
1.1656 + """
1.1657 +
1.1658 + attrs = set()
1.1659 +
1.1660 + # The class types expose class attributes either directly or via
1.1661 + # instances.
1.1662 +
1.1663 + for object_type in class_types:
1.1664 + ref = self.importer.get_class_attribute(object_type, attrname)
1.1665 + if ref:
1.1666 + attrs.add(("<class>", object_type, ref))
1.1667 +
1.1668 + # Add any distinct instance attributes that would be provided
1.1669 + # by instances also providing indirect class attribute access.
1.1670 +
1.1671 + for ref in self.importer.get_instance_attributes(object_type, attrname):
1.1672 + attrs.add(("<instance>", object_type, ref))
1.1673 +
1.1674 + # The instance-only types expose instance attributes, but although
1.1675 + # classes are excluded as potential accessors (since they do not provide
1.1676 + # the instance attributes), the class types may still provide some
1.1677 + # attributes.
1.1678 +
1.1679 + for object_type in instance_types:
1.1680 + instance_attrs = self.importer.get_instance_attributes(object_type, attrname)
1.1681 +
1.1682 + if instance_attrs:
1.1683 + for ref in instance_attrs:
1.1684 + attrs.add(("<instance>", object_type, ref))
1.1685 + else:
1.1686 + ref = self.importer.get_class_attribute(object_type, attrname)
1.1687 + if ref:
1.1688 + attrs.add(("<class>", object_type, ref))
1.1689 +
1.1690 + # Module types expose module attributes for module accessors.
1.1691 +
1.1692 + for object_type in module_types:
1.1693 + ref = self.importer.get_module_attribute(object_type, attrname)
1.1694 + if ref:
1.1695 + attrs.add(("<module>", object_type, ref))
1.1696 +
1.1697 + return attrs
1.1698 +
1.1699 +# vim: tabstop=4 expandtab shiftwidth=4