Lichen

Changeset

728:8e3fdfc663c9
2017-03-13 Paul Boddie raw files shortlog changelog graph Merged changes from the default branch. normal-function-parameters
templates/ops.c (file) translator.py (file)
     1.1 --- a/common.py	Sat Mar 11 01:04:34 2017 +0100
     1.2 +++ b/common.py	Mon Mar 13 17:53:19 2017 +0100
     1.3 @@ -129,7 +129,6 @@
     1.4  
     1.5          self.astnode = None
     1.6          self.encoding = None
     1.7 -        self.iterators = {}
     1.8          self.temp = {}
     1.9          self.lambdas = {}
    1.10  
    1.11 @@ -230,19 +229,6 @@
    1.12      def next_literal(self):
    1.13          self.literals[self.get_namespace_path()] += 1
    1.14  
    1.15 -    # Temporary iterator naming.
    1.16 -
    1.17 -    def get_iterator_path(self):
    1.18 -        return self.in_function and self.get_namespace_path() or self.name
    1.19 -
    1.20 -    def get_iterator_name(self):
    1.21 -        path = self.get_iterator_path()
    1.22 -        init_item(self.iterators, path, lambda: 0)
    1.23 -        return "$i%d" % self.iterators[path]
    1.24 -
    1.25 -    def next_iterator(self):
    1.26 -        self.iterators[self.get_iterator_path()] += 1
    1.27 -
    1.28      # Temporary variable naming.
    1.29  
    1.30      def get_temporary_name(self):
    1.31 @@ -565,17 +551,32 @@
    1.32          the iterator, producing a replacement node for the original.
    1.33          """
    1.34  
    1.35 +        t0 = self.get_temporary_name()
    1.36 +        self.next_temporary()
    1.37 +        t1 = self.get_temporary_name()
    1.38 +        self.next_temporary()
    1.39 +        i0 = self.get_temporary_name()
    1.40 +        self.next_temporary()
    1.41 +
    1.42          node = compiler.ast.Stmt([
    1.43  
    1.44 -            # <next> = {n.list}.__iter__().next
    1.45 +            # <t0> = {n.list}
    1.46 +            # <t1> = <t0>.__iter__()
    1.47 +            # <i0> = <t1>.next
    1.48  
    1.49              compiler.ast.Assign(
    1.50 -                [compiler.ast.AssName(self.get_iterator_name(), "OP_ASSIGN")],
    1.51 -                compiler.ast.Getattr(
    1.52 -                    compiler.ast.CallFunc(
    1.53 -                        compiler.ast.Getattr(n.list, "__iter__"),
    1.54 -                        []
    1.55 -                        ), "next")),
    1.56 +                [compiler.ast.AssName(t0, "OP_ASSIGN")],
    1.57 +                n.list),
    1.58 +
    1.59 +            compiler.ast.Assign(
    1.60 +                [compiler.ast.AssName(t1, "OP_ASSIGN")],
    1.61 +                compiler.ast.CallFunc(
    1.62 +                    compiler.ast.Getattr(compiler.ast.Name(t0), "__iter__"),
    1.63 +                    [])),
    1.64 +
    1.65 +            compiler.ast.Assign(
    1.66 +                [compiler.ast.AssName(i0, "OP_ASSIGN")],
    1.67 +                compiler.ast.Getattr(compiler.ast.Name(t1), "next")),
    1.68  
    1.69              # try:
    1.70              #     while True:
    1.71 @@ -591,7 +592,7 @@
    1.72                          compiler.ast.Assign(
    1.73                              [n.assign],
    1.74                              compiler.ast.CallFunc(
    1.75 -                                compiler.ast.Name(self.get_iterator_name()),
    1.76 +                                compiler.ast.Name(i0),
    1.77                                  []
    1.78                                  )),
    1.79                          n.body]),
    1.80 @@ -600,7 +601,6 @@
    1.81                  None)
    1.82              ])
    1.83  
    1.84 -        self.next_iterator()
    1.85          self.process_structure_node(node)
    1.86  
    1.87      def process_literal_sequence_node(self, n, name, ref, cls):
    1.88 @@ -987,6 +987,185 @@
    1.89  def same(s1, s2):
    1.90      return set(s1) == set(s2)
    1.91  
    1.92 +def order_dependencies(all_depends):
    1.93 +
    1.94 +    """
    1.95 +    Produce a dependency ordering for the 'all_depends' mapping. This mapping
    1.96 +    has the form "A depends on B, C...". The result will order A, B, C, and so
    1.97 +    on.
    1.98 +    """
    1.99 +
   1.100 +    usage = init_reverse_dependencies(all_depends)
   1.101 +
   1.102 +    # Produce an ordering by obtaining exposed items (required by items already
   1.103 +    # processed) and putting them at the start of the list.
   1.104 +
   1.105 +    ordered = []
   1.106 +
   1.107 +    while usage:
   1.108 +        have_next = False
   1.109 +
   1.110 +        for key, n in usage.items():
   1.111 +
   1.112 +            # Add items needed by no other items to the ordering.
   1.113 +
   1.114 +            if not n:
   1.115 +                remove_dependency(key, all_depends, usage, ordered)
   1.116 +                have_next = True
   1.117 +
   1.118 +        if not have_next:
   1.119 +            raise ValueError, usage
   1.120 +
   1.121 +    return ordered
   1.122 +
   1.123 +def order_dependencies_partial(all_depends):
   1.124 +
   1.125 +    """
   1.126 +    Produce a dependency ordering for the 'all_depends' mapping. This mapping
   1.127 +    has the form "A depends on B, C...". The result will order A, B, C, and so
   1.128 +    on. Where cycles exist, they will be broken and a partial ordering returned.
   1.129 +    """
   1.130 +
   1.131 +    usage = init_reverse_dependencies(all_depends)
   1.132 +
   1.133 +    # Duplicate the dependencies for subsequent modification.
   1.134 +
   1.135 +    new_depends = {}
   1.136 +    for key, values in all_depends.items():
   1.137 +        new_depends[key] = set(values)
   1.138 +
   1.139 +    all_depends = new_depends
   1.140 +
   1.141 +    # Produce an ordering by obtaining exposed items (required by items already
   1.142 +    # processed) and putting them at the start of the list.
   1.143 +
   1.144 +    ordered = []
   1.145 +
   1.146 +    while usage:
   1.147 +        least = None
   1.148 +        least_key = None
   1.149 +
   1.150 +        for key, n in usage.items():
   1.151 +
   1.152 +            # Add items needed by no other items to the ordering.
   1.153 +
   1.154 +            if not n:
   1.155 +                remove_dependency(key, all_depends, usage, ordered)
   1.156 +                least = 0
   1.157 +
   1.158 +            # When breaking cycles, note the least used items.
   1.159 +
   1.160 +            elif least is None or len(n) < least:
   1.161 +                least_key = key
   1.162 +                least = len(n)
   1.163 +
   1.164 +        if least:
   1.165 +            transfer_dependencies(least_key, all_depends, usage, ordered)
   1.166 +
   1.167 +    return ordered
   1.168 +
   1.169 +def init_reverse_dependencies(all_depends):
   1.170 +
   1.171 +    """
   1.172 +    From 'all_depends', providing a mapping of the form "A depends on B, C...",
   1.173 +    record the reverse dependencies, making a mapping of the form
   1.174 +    "B is needed by A", "C is needed by A", and so on.
   1.175 +    """
   1.176 +
   1.177 +    usage = {}
   1.178 +
   1.179 +    # Record path-based dependencies.
   1.180 +
   1.181 +    for key in all_depends.keys():
   1.182 +        usage[key] = set()
   1.183 +
   1.184 +    for key, depends in all_depends.items():
   1.185 +        for depend in depends:
   1.186 +            init_item(usage, depend, set)
   1.187 +            usage[depend].add(key)
   1.188 +
   1.189 +    return usage
   1.190 +
   1.191 +def transfer_dependencies(key, all_depends, usage, ordered):
   1.192 +
   1.193 +    """
   1.194 +    Transfer items needed by 'key' to those items needing 'key', found using
   1.195 +    'all_depends', and updating 'usage'. Insert 'key' into the 'ordered'
   1.196 +    collection of dependencies.
   1.197 +
   1.198 +    If "A is needed by X" and "B is needed by A", then transferring items needed
   1.199 +    by A will cause "B is needed by X" to be recorded as a consequence.
   1.200 +
   1.201 +    Transferring items also needs to occur in the reverse mapping, so that
   1.202 +    "A needs B" and "X needs A", then the consequence must be recorded as
   1.203 +    "X needs B".
   1.204 +    """
   1.205 +
   1.206 +    ordered.insert(0, key)
   1.207 +
   1.208 +    needing = usage[key]                        # A is needed by X
   1.209 +    needed = all_depends.get(key)               # A needs B
   1.210 +
   1.211 +    if needing:
   1.212 +        for depend in needing:
   1.213 +            l = all_depends.get(depend)
   1.214 +            if not l:
   1.215 +                continue
   1.216 +
   1.217 +            l.remove(key)                       # X needs (A)
   1.218 +
   1.219 +            if needed:
   1.220 +                l.update(needed)                # X needs B...
   1.221 +
   1.222 +                # Prevent self references.
   1.223 +
   1.224 +                if depend in needed:
   1.225 +                    l.remove(depend)
   1.226 +
   1.227 +    if needed:
   1.228 +        for depend in needed:
   1.229 +            l = usage.get(depend)
   1.230 +            if not l:
   1.231 +                continue
   1.232 +
   1.233 +            l.remove(key)                       # B is needed by (A)
   1.234 +            l.update(needing)                   # B is needed by X...
   1.235 +
   1.236 +            # Prevent self references.
   1.237 +
   1.238 +            if depend in needing:
   1.239 +                l.remove(depend)
   1.240 +
   1.241 +    if needed:
   1.242 +        del all_depends[key]
   1.243 +    del usage[key]
   1.244 +
   1.245 +def remove_dependency(key, all_depends, usage, ordered):
   1.246 +
   1.247 +    """
   1.248 +    Remove 'key', found in 'all_depends', from 'usage', inserting it into the
   1.249 +    'ordered' collection of dependencies.
   1.250 +
   1.251 +    Given that 'usage' for a given key A would indicate that "A needs <nothing>"
   1.252 +    upon removing A from 'usage', the outcome is that all keys needing A will
   1.253 +    have A removed from their 'usage' records.
   1.254 +
   1.255 +    So, if "B needs A", removing A will cause "B needs <nothing>" to be recorded
   1.256 +    as a consequence.
   1.257 +    """
   1.258 +
   1.259 +    ordered.insert(0, key)
   1.260 +
   1.261 +    depends = all_depends.get(key)
   1.262 +
   1.263 +    # Reduce usage of the referenced items.
   1.264 +
   1.265 +    if depends:
   1.266 +        for depend in depends:
   1.267 +            usage[depend].remove(key)
   1.268 +
   1.269 +    del usage[key]
   1.270 +
   1.271  # General input/output.
   1.272  
   1.273  def readfile(filename):
     2.1 --- a/deducer.py	Sat Mar 11 01:04:34 2017 +0100
     2.2 +++ b/deducer.py	Mon Mar 13 17:53:19 2017 +0100
     2.3 @@ -22,7 +22,7 @@
     2.4  from common import first, get_assigned_attributes, \
     2.5                     get_attrname_from_location, get_attrnames, \
     2.6                     get_invoked_attributes, get_name_path, init_item, \
     2.7 -                   sorted_output, CommonOutput
     2.8 +                   order_dependencies_partial, sorted_output, CommonOutput
     2.9  from encoders import encode_access_location, encode_constrained, \
    2.10                       encode_instruction, encode_location, encode_usage, \
    2.11                       get_kinds, test_label_for_kind, test_label_for_type
    2.12 @@ -146,6 +146,7 @@
    2.13          self.init_accessors()
    2.14          self.init_accesses()
    2.15          self.init_aliases()
    2.16 +        self.init_alias_network()
    2.17          self.modify_mutated_attributes()
    2.18          self.identify_references()
    2.19          self.classify_accessors()
    2.20 @@ -903,9 +904,11 @@
    2.21                          access_location = (path, None, attrname_str, 0)
    2.22  
    2.23                      # Plain name accesses do not employ attributes and are
    2.24 -                    # ignored.
    2.25 +                    # ignored. Whether they are invoked is of interest, however.
    2.26  
    2.27                      if not attrname_str:
    2.28 +                        if invocation:
    2.29 +                            self.reference_invocations[access_location] = invocation
    2.30                          continue
    2.31  
    2.32                      attrnames = get_attrnames(attrname_str)
    2.33 @@ -1016,6 +1019,21 @@
    2.34          self.alias_index[accessor_location] = updated_locations
    2.35          return updated_locations
    2.36  
    2.37 +    def init_alias_network(self):
    2.38 +
    2.39 +        """
    2.40 +        Initialise a network of aliases, their initialising accesses, and the
    2.41 +        accessors supporting those accesses.
    2.42 +        """
    2.43 +
    2.44 +        self.alias_network = {}
    2.45 +        self.alias_network.update(self.alias_index)
    2.46 +
    2.47 +        for accessor_location, access_locations in self.alias_index.items():
    2.48 +            for access_location in access_locations:
    2.49 +                if not self.alias_network.has_key(access_location):
    2.50 +                    self.alias_network[access_location] = self.get_accessors_for_access(access_location)
    2.51 +
    2.52      # Attribute mutation for types.
    2.53  
    2.54      def modify_mutated_attributes(self):
    2.55 @@ -1350,8 +1368,9 @@
    2.56          # Aliased name definitions. All aliases with usage will have been
    2.57          # defined, but they may be refined according to referenced accesses.
    2.58  
    2.59 -        for accessor_location in self.alias_index.keys():
    2.60 -            self.record_types_for_alias(accessor_location)
    2.61 +        for location in order_dependencies_partial(self.alias_network):
    2.62 +            if self.alias_index.has_key(location):
    2.63 +                self.record_types_for_alias(location)
    2.64  
    2.65          # Update accesses employing aliases.
    2.66  
    2.67 @@ -1518,6 +1537,9 @@
    2.68          if not attrname:
    2.69              return
    2.70  
    2.71 +        invocation = access_location in self.reference_invocations
    2.72 +        assignment = access_location in self.reference_assignments
    2.73 +
    2.74          # Collect all suggested types for the accessors. Accesses may
    2.75          # require accessors from of a subset of the complete set of types.
    2.76  
    2.77 @@ -1547,7 +1569,7 @@
    2.78  
    2.79              else:
    2.80                  self.init_definition_details(location)
    2.81 -                self.record_types_for_usage(location, [(attrname, False, False)])
    2.82 +                self.record_types_for_usage(location, [(attrname, invocation, assignment)])
    2.83  
    2.84              constrained = location in self.accessor_constrained and constrained
    2.85  
    2.86 @@ -1609,36 +1631,64 @@
    2.87          have_access = self.provider_class_types.has_key(accessor_location)
    2.88  
    2.89          # With an access, attempt to narrow the existing selection of provider
    2.90 -        # types.
    2.91 +        # types. Invocations attempt to find return value information, with
    2.92 +        # instance return values also yielding class providers (since attributes
    2.93 +        # on instances could be provided by classes).
    2.94  
    2.95          if have_access:
    2.96              provider_class_types = self.provider_class_types[accessor_location]
    2.97              provider_instance_types = self.provider_instance_types[accessor_location]
    2.98              provider_module_types = self.provider_module_types[accessor_location]
    2.99  
   2.100 +            accessor_class_types = self.accessor_class_types[accessor_location]
   2.101 +            accessor_instance_types = self.accessor_instance_types[accessor_location]
   2.102 +            accessor_module_types = self.accessor_module_types[accessor_location]
   2.103 +
   2.104              # Find details for any corresponding access.
   2.105  
   2.106 -            all_class_types = set()
   2.107 -            all_instance_types = set()
   2.108 -            all_module_types = set()
   2.109 +            new_provider_class_types = set()
   2.110 +            new_provider_instance_types = set()
   2.111 +            new_provider_module_types = set()
   2.112 +
   2.113 +            new_accessor_class_types = set()
   2.114 +            new_accessor_instance_types = set()
   2.115 +            new_accessor_module_types = set()
   2.116  
   2.117              for access_location in self.alias_index[accessor_location]:
   2.118                  location, name, attrnames, access_number = access_location
   2.119 +                invocation = self.reference_invocations.get(access_location)
   2.120 +
   2.121 +                attrnames = attrnames and attrnames.split(".")
   2.122 +                remaining = attrnames and len(attrnames) > 1
   2.123 +
   2.124 +                # Alias has remaining attributes: reference details do not
   2.125 +                # correspond to the accessor; the remaining attributes would
   2.126 +                # need to be traversed first.
   2.127 +
   2.128 +                if remaining:
   2.129 +                    return
   2.130  
   2.131                  # Alias references an attribute access.
   2.132  
   2.133                  if attrnames:
   2.134  
   2.135 -                    # Obtain attribute references for the access.
   2.136 -
   2.137 -                    attrs = []
   2.138 -                    for _attrtype, object_type, attr in self.referenced_attrs[access_location]:
   2.139 -                        attrs.append(attr)
   2.140 -
   2.141 -                    # Separate the different attribute types.
   2.142 -
   2.143 -                    (class_types, instance_types, module_types,
   2.144 -                        function_types, var_types) = separate_types(attrs)
   2.145 +                    # Obtain references and attribute types for the access.
   2.146 +
   2.147 +                    attrs = self.get_references_for_access(access_location)
   2.148 +
   2.149 +                    # Where no specific attributes are defined, do not attempt
   2.150 +                    # to refine the alias's types.
   2.151 +
   2.152 +                    if not attrs:
   2.153 +                        return
   2.154 +
   2.155 +                    # Invocations converting class accessors to instances do not
   2.156 +                    # change the nature of class providers.
   2.157 +
   2.158 +                    provider_attrs = self.convert_invocation_providers(attrs, invocation)
   2.159 +
   2.160 +                    (class_types, instance_types, module_types, function_types,
   2.161 +                        var_types) = separate_types(provider_attrs)
   2.162  
   2.163                      # Where non-accessor types are found, do not attempt to refine
   2.164                      # the defined accessor types.
   2.165 @@ -1650,6 +1700,26 @@
   2.166                      instance_types = set(provider_instance_types).intersection(instance_types)
   2.167                      module_types = set(provider_module_types).intersection(module_types)
   2.168  
   2.169 +                    new_provider_class_types.update(class_types)
   2.170 +                    new_provider_instance_types.update(instance_types)
   2.171 +                    new_provider_module_types.update(module_types)
   2.172 +
   2.173 +                    # Accessors are updated separately, employing invocation
   2.174 +                    # result details.
   2.175 +
   2.176 +                    accessor_attrs = self.convert_invocations(attrs, invocation)
   2.177 +
   2.178 +                    (class_types, instance_types, module_types, function_types,
   2.179 +                        var_types) = separate_types(accessor_attrs)
   2.180 +
   2.181 +                    class_types = set(accessor_class_types).intersection(class_types)
   2.182 +                    instance_types = set(accessor_instance_types).intersection(instance_types)
   2.183 +                    module_types = set(accessor_module_types).intersection(module_types)
   2.184 +
   2.185 +                    new_accessor_class_types.update(class_types)
   2.186 +                    new_accessor_instance_types.update(instance_types)
   2.187 +                    new_accessor_module_types.update(module_types)
   2.188 +
   2.189                  # Alias references a name, not an access.
   2.190  
   2.191                  else:
   2.192 @@ -1657,8 +1727,32 @@
   2.193  
   2.194                      attr = self.get_initialised_name(access_location)
   2.195                      if attr:
   2.196 -                        (class_types, instance_types, module_types,
   2.197 -                            _function_types, _var_types) = separate_types([attr])
   2.198 +                        attrs = [attr]
   2.199 +                        provider_attrs = self.convert_invocation_providers(attrs, invocation)
   2.200 +
   2.201 +                        (class_types, instance_types, module_types, function_types,
   2.202 +                            var_types) = separate_types(provider_attrs)
   2.203 +
   2.204 +                        class_types = set(provider_class_types).intersection(class_types)
   2.205 +                        instance_types = set(provider_instance_types).intersection(instance_types)
   2.206 +                        module_types = set(provider_module_types).intersection(module_types)
   2.207 +
   2.208 +                        new_provider_class_types.update(class_types)
   2.209 +                        new_provider_instance_types.update(instance_types)
   2.210 +                        new_provider_module_types.update(module_types)
   2.211 +
   2.212 +                        accessor_attrs = self.convert_invocations(attrs, invocation)
   2.213 +
   2.214 +                        (class_types, instance_types, module_types, function_types,
   2.215 +                            var_types) = separate_types(accessor_attrs)
   2.216 +
   2.217 +                        class_types = set(accessor_class_types).intersection(class_types)
   2.218 +                        instance_types = set(accessor_instance_types).intersection(instance_types)
   2.219 +                        module_types = set(accessor_module_types).intersection(module_types)
   2.220 +
   2.221 +                        new_accessor_class_types.update(class_types)
   2.222 +                        new_accessor_instance_types.update(instance_types)
   2.223 +                        new_accessor_module_types.update(module_types)
   2.224  
   2.225                      # Where no further information is found, do not attempt to
   2.226                      # refine the defined accessor types.
   2.227 @@ -1666,16 +1760,15 @@
   2.228                      else:
   2.229                          return
   2.230  
   2.231 -                all_class_types.update(class_types)
   2.232 -                all_instance_types.update(instance_types)
   2.233 -                all_module_types.update(module_types)
   2.234 -
   2.235              # Record refined type details for the alias as an accessor.
   2.236  
   2.237              self.init_definition_details(accessor_location)
   2.238 -            self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False)
   2.239 +            self.update_provider_types(accessor_location, new_provider_class_types, new_provider_instance_types, new_provider_module_types)
   2.240 +            self.update_accessor_types(accessor_location, new_accessor_class_types, new_accessor_instance_types, new_accessor_module_types)
   2.241  
   2.242          # Without an access, attempt to identify references for the alias.
   2.243 +        # Invocations convert classes to instances and also attempt to find
   2.244 +        # return value information.
   2.245  
   2.246          else:
   2.247              refs = set()
   2.248 @@ -1688,6 +1781,8 @@
   2.249                      access_location = self.const_accesses[access_location]
   2.250  
   2.251                  location, name, attrnames, access_number = access_location
   2.252 +                invocation = self.reference_invocations.get(access_location)
   2.253 +
   2.254                  attrnames = attrnames and attrnames.split(".")
   2.255                  remaining = attrnames and len(attrnames) > 1
   2.256  
   2.257 @@ -1700,31 +1795,94 @@
   2.258  
   2.259                  # Alias references an attribute access.
   2.260  
   2.261 -                attrname = attrnames and attrnames[0]
   2.262 -
   2.263 -                if attrname:
   2.264 -                    attrs = []
   2.265 -                    for attrtype, object_type, attr in self.referenced_attrs[access_location]:
   2.266 -                        attrs.append(attr)
   2.267 +                if attrnames:
   2.268 +
   2.269 +                    # Obtain references and attribute types for the access.
   2.270 +
   2.271 +                    attrs = self.get_references_for_access(access_location)
   2.272 +                    attrs = self.convert_invocations(attrs, invocation)
   2.273                      refs.update(attrs)
   2.274  
   2.275                  # Alias references a name, not an access.
   2.276  
   2.277                  else:
   2.278 +
   2.279 +                    # Obtain initialiser information.
   2.280 +
   2.281                      attr = self.get_initialised_name(access_location)
   2.282 -                    attrs = attr and [attr] or []
   2.283 -                    if not attrs and self.provider_class_types.has_key(access_location):
   2.284 +                    if attr:
   2.285 +                        refs.update(self.convert_invocations([attr], invocation))
   2.286 +
   2.287 +                    # Obtain provider information.
   2.288 +
   2.289 +                    elif self.provider_class_types.has_key(access_location):
   2.290                          class_types = self.provider_class_types[access_location]
   2.291                          instance_types = self.provider_instance_types[access_location]
   2.292                          module_types = self.provider_module_types[access_location]
   2.293 -                        attrs = combine_types(class_types, instance_types, module_types)
   2.294 -                    if attrs:
   2.295 -                        refs.update(attrs)
   2.296 +
   2.297 +                        types = combine_types(class_types, instance_types, module_types)
   2.298 +                        refs.update(self.convert_invocation_providers(types, invocation))
   2.299  
   2.300              # Record reference details for the alias separately from accessors.
   2.301  
   2.302              self.referenced_objects[accessor_location] = refs
   2.303  
   2.304 +    def get_references_for_access(self, access_location):
   2.305 +
   2.306 +        "Return the references identified for 'access_location'."
   2.307 +
   2.308 +        attrs = []
   2.309 +        for attrtype, object_type, attr in self.referenced_attrs[access_location]:
   2.310 +            attrs.append(attr)
   2.311 +        return attrs
   2.312 +
   2.313 +    def convert_invocation_providers(self, refs, invocation):
   2.314 +
   2.315 +        """
   2.316 +        Convert 'refs' to providers corresponding to the results of invoking
   2.317 +        each of the given references, if 'invocation' is set to a true value.
   2.318 +        """
   2.319 +
   2.320 +        if not invocation:
   2.321 +            return refs
   2.322 +
   2.323 +        providers = set()
   2.324 +
   2.325 +        for ref in refs:
   2.326 +            ref = self.convert_invocation_provider(ref)
   2.327 +            if ref.has_kind("<instance>"):
   2.328 +                providers.add(Reference("<class>", ref.get_origin()))
   2.329 +            providers.add(ref)
   2.330 +
   2.331 +        return providers
   2.332 +
   2.333 +    def convert_invocation_provider(self, ref):
   2.334 +
   2.335 +        "Convert 'ref' to a provider appropriate to its invocation result."
   2.336 +
   2.337 +        if ref and ref.has_kind("<class>"):
   2.338 +            return ref
   2.339 +
   2.340 +        return Reference("<var>")
   2.341 +
   2.342 +    def convert_invocations(self, refs, invocation):
   2.343 +
   2.344 +        """
   2.345 +        Convert 'refs' to invocation results if 'invocation' is set to a true
   2.346 +        value.
   2.347 +        """
   2.348 +
   2.349 +        return invocation and map(self.convert_invocation, refs) or refs
   2.350 +
   2.351 +    def convert_invocation(self, ref):
   2.352 +
   2.353 +        "Convert 'ref' to its invocation result."
   2.354 +
   2.355 +        if ref and ref.has_kind("<class>"):
   2.356 +            return ref.instance_of()
   2.357 +
   2.358 +        return Reference("<var>")
   2.359 +
   2.360      def get_initialised_name(self, access_location):
   2.361  
   2.362          """
   2.363 @@ -1770,15 +1928,13 @@
   2.364  
   2.365          # Update the type details for the location.
   2.366  
   2.367 -        self.provider_class_types[location].update(class_types)
   2.368 -        self.provider_instance_types[location].update(instance_types)
   2.369 -        self.provider_module_types[location].update(module_types)
   2.370 +        self.update_provider_types(location, class_types, instance_types, module_types)
   2.371  
   2.372          # Class types support classes and instances as accessors.
   2.373          # Instance-only and module types support only their own kinds as
   2.374          # accessors.
   2.375  
   2.376 -        path, name, version, attrnames = location
   2.377 +        path, name, attrnames, version = location
   2.378  
   2.379          if invocations:
   2.380              class_only_types = self.filter_for_invocations(class_types, invocations)
   2.381 @@ -1802,6 +1958,28 @@
   2.382          if constrained:
   2.383              self.accessor_constrained.add(location)
   2.384  
   2.385 +    def update_provider_types(self, location, class_types, instance_types, module_types):
   2.386 +
   2.387 +        """
   2.388 +        Update provider types for the given 'location', adding 'class_types',
   2.389 +        'instance_types' and 'module_types' to those already stored.
   2.390 +        """
   2.391 +
   2.392 +        self.provider_class_types[location].update(class_types)
   2.393 +        self.provider_instance_types[location].update(instance_types)
   2.394 +        self.provider_module_types[location].update(module_types)
   2.395 +
   2.396 +    def update_accessor_types(self, location, class_types, instance_types, module_types):
   2.397 +
   2.398 +        """
   2.399 +        Update accessor types for the given 'location', adding 'class_types',
   2.400 +        'instance_types' and 'module_types' to those already stored.
   2.401 +        """
   2.402 +
   2.403 +        self.accessor_class_types[location].update(class_types)
   2.404 +        self.accessor_instance_types[location].update(instance_types)
   2.405 +        self.accessor_module_types[location].update(module_types)
   2.406 +
   2.407      def filter_for_invocations(self, class_types, attrnames):
   2.408  
   2.409          """
     3.1 --- a/importer.py	Sat Mar 11 01:04:34 2017 +0100
     3.2 +++ b/importer.py	Mon Mar 13 17:53:19 2017 +0100
     3.3 @@ -23,7 +23,7 @@
     3.4  from errors import ProgramError
     3.5  from os.path import exists, extsep, getmtime, join
     3.6  from os import listdir, makedirs, remove
     3.7 -from common import init_item, readfile, writefile
     3.8 +from common import init_item, order_dependencies, readfile, writefile
     3.9  from modules import CachedModule
    3.10  from referencing import Reference
    3.11  import inspector
    3.12 @@ -673,44 +673,10 @@
    3.13  
    3.14          self.condense_dependencies()
    3.15  
    3.16 -        # Record the number of modules using or depending on each module.
    3.17 -
    3.18 -        usage = {}
    3.19 -
    3.20 -        # Record path-based dependencies.
    3.21 -
    3.22 -        for path in self.depends.keys():
    3.23 -            usage[path] = set()
    3.24 -
    3.25 -        for path, depends in self.depends.items():
    3.26 -            for origin in depends:
    3.27 -                init_item(usage, origin, set)
    3.28 -                usage[origin].add(path)
    3.29 -
    3.30 -        # Produce an ordering by obtaining exposed modules (required by modules
    3.31 -        # already processed) and putting them at the start of the list.
    3.32 -
    3.33 -        ordered = []
    3.34 -
    3.35 -        while usage:
    3.36 -            have_next = False
    3.37 -
    3.38 -            for path, n in usage.items():
    3.39 -                if not n:
    3.40 -                    ordered.insert(0, path)
    3.41 -                    depends = self.depends.get(path)
    3.42 -
    3.43 -                    # Reduce usage of the referenced objects.
    3.44 -
    3.45 -                    if depends:
    3.46 -                        for origin in depends:
    3.47 -                            usage[origin].remove(path)
    3.48 -
    3.49 -                    del usage[path]
    3.50 -                    have_next = True
    3.51 -
    3.52 -            if not have_next:
    3.53 -                raise ProgramError("Modules with unresolvable dependencies exist: %s" % ", ".join(usage.keys()))
    3.54 +        try:
    3.55 +            ordered = order_dependencies(self.depends)
    3.56 +        except ValueError, exc:
    3.57 +            raise ProgramError("Modules with unresolvable dependencies exist: %s" % ", ".join(exc.args[0].keys()))
    3.58  
    3.59          if "__main__" in ordered:
    3.60              ordered.remove("__main__")
     4.1 --- a/inspector.py	Sat Mar 11 01:04:34 2017 +0100
     4.2 +++ b/inspector.py	Mon Mar 13 17:53:19 2017 +0100
     4.3 @@ -766,7 +766,7 @@
     4.4          if isinstance(name_ref, ResolvedNameRef) and name_ref.has_kind("<class>"):
     4.5              return InstanceRef(name_ref.reference().instance_of())
     4.6  
     4.7 -        elif isinstance(name_ref, NameRef):
     4.8 +        elif isinstance(name_ref, (NameRef, AccessRef)):
     4.9              return InvocationRef(name_ref)
    4.10  
    4.11          # Provide a general reference to indicate that something is produced
    4.12 @@ -902,7 +902,8 @@
    4.13          branches = self.trackers[-1].tracking_name(name)
    4.14          if branches:
    4.15              self.record_branches_for_access(branches, name, None)
    4.16 -            return self.record_access_details(name, None, None, None)
    4.17 +            return self.record_access_details(name, None, self.in_assignment,
    4.18 +                                              self.in_invocation)
    4.19          return None
    4.20  
    4.21      def process_operator_chain(self, nodes, fn):
     5.1 --- a/resolving.py	Sat Mar 11 01:04:34 2017 +0100
     5.2 +++ b/resolving.py	Mon Mar 13 17:53:19 2017 +0100
     5.3 @@ -283,13 +283,12 @@
     5.4                          # but they may be resolvable later.
     5.5  
     5.6                          if not ref:
     5.7 -                            if not invocation:
     5.8  
     5.9 -                                # Record the path used for tracking purposes
    5.10 -                                # alongside original name, attribute and access
    5.11 -                                # number details.
    5.12 +                            # Record the path used for tracking purposes
    5.13 +                            # alongside original name, attribute and access
    5.14 +                            # number details.
    5.15  
    5.16 -                                aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
    5.17 +                            aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
    5.18  
    5.19                              continue
    5.20  
    5.21 @@ -297,33 +296,30 @@
    5.22  
    5.23                      elif isinstance(name_ref, LocalNameRef):
    5.24                          key = "%s.%s" % (path, name_ref.name)
    5.25 -                        origin = self.name_references.get(key)
    5.26 +                        ref = self.name_references.get(key)
    5.27  
    5.28                          # Accesses that do not refer to known static objects
    5.29                          # cannot be resolved, but they may be resolvable later.
    5.30  
    5.31 -                        if not origin:
    5.32 -                            if not invocation:
    5.33 +                        if not ref:
    5.34  
    5.35 -                                # Record the path used for tracking purposes
    5.36 -                                # alongside original name, attribute and access
    5.37 -                                # number details.
    5.38 +                            # Record the path used for tracking purposes
    5.39 +                            # alongside original name, attribute and access
    5.40 +                            # number details.
    5.41  
    5.42 -                                aliased_names[i] = path, name_ref.name, None, name_ref.number
    5.43 +                            aliased_names[i] = path, name_ref.name, None, name_ref.number
    5.44  
    5.45                              continue
    5.46  
    5.47 -                        ref = self.get_resolved_object(origin)
    5.48 +                        ref = self.get_resolved_object(ref.get_origin())
    5.49                          if not ref:
    5.50                              continue
    5.51  
    5.52                      elif isinstance(name_ref, NameRef):
    5.53                          key = "%s.%s" % (path, name_ref.name)
    5.54 -                        origin = self.name_references.get(key)
    5.55 -                        if not origin:
    5.56 -                            continue
    5.57 +                        ref = self.name_references.get(key)
    5.58  
    5.59 -                        ref = self.get_resolved_object(origin)
    5.60 +                        ref = ref and self.get_resolved_object(ref.get_origin())
    5.61                          if not ref:
    5.62                              continue
    5.63  
    5.64 @@ -338,7 +334,7 @@
    5.65  
    5.66                      # Convert class invocations to instances.
    5.67  
    5.68 -                    if ref and invocation:
    5.69 +                    if ref and invocation or ref.has_kind("<invoke>"):
    5.70                          ref = self.convert_invocation(ref)
    5.71  
    5.72                      if ref and not ref.has_kind("<var>"):
     6.1 --- a/templates/ops.c	Sat Mar 11 01:04:34 2017 +0100
     6.2 +++ b/templates/ops.c	Mon Mar 13 17:53:19 2017 +0100
     6.3 @@ -34,17 +34,17 @@
     6.4  
     6.5  __attr __load_static_ignore(__ref obj)
     6.6  {
     6.7 -    return (__attr) {.value=obj};
     6.8 +    return __ATTRVALUE(obj);
     6.9  }
    6.10  
    6.11  __attr __load_static_replace(__ref context, __ref obj)
    6.12  {
    6.13 -    return __update_context(context, (__attr) {.value=obj});
    6.14 +    return __update_context(context, __ATTRVALUE(obj));
    6.15  }
    6.16  
    6.17  __attr __load_static_test(__ref context, __ref obj)
    6.18  {
    6.19 -    return __test_context(context, (__attr) {.value=obj});
    6.20 +    return __test_context(context, __ATTRVALUE(obj));
    6.21  }
    6.22  
    6.23  /* Direct retrieval operations, returning and setting attributes. */
    6.24 @@ -281,9 +281,9 @@
    6.25  {
    6.26      /* Set the local context to the specified context if appropriate. */
    6.27  
    6.28 -    if (__test_context_update(context, (__attr) {.value=value}))
    6.29 +    if (__test_context_update(context, __ATTRVALUE(value)))
    6.30          contexts[target] = context;
    6.31 -    return (__attr) {.value=value};
    6.32 +    return __ATTRVALUE(value);
    6.33  }
    6.34  
    6.35  /* Context testing for invocations. */
     7.1 --- a/translator.py	Sat Mar 11 01:04:34 2017 +0100
     7.2 +++ b/translator.py	Mon Mar 13 17:53:19 2017 +0100
     7.3 @@ -869,7 +869,7 @@
     7.4  
     7.5              # Produce an appropriate access to an attribute's value.
     7.6  
     7.7 -            name_to_value = "%s.value" % name
     7.8 +            name_to_value = "%s.value" % encode_path(name)
     7.9  
    7.10              # Write a test that raises a TypeError upon failure.
    7.11  
    7.12 @@ -1440,7 +1440,7 @@
    7.13          # Find any invocation or alias details.
    7.14  
    7.15          name = self.get_name_for_tracking(n.name, is_global=is_global)
    7.16 -        location = not expr and self.get_access_location(name)
    7.17 +        location = not expr and self.get_access_location(name) or None
    7.18  
    7.19          # Mark any local assignments as volatile in exception blocks.
    7.20