Lichen

Changeset

709:26281477b9c1
2017-03-12 Paul Boddie raw files shortlog changelog graph Support deduction on invoked alias-initialising accesses.
deducer.py (file) inspector.py (file) resolving.py (file)
     1.1 --- a/deducer.py	Sun Mar 12 17:46:28 2017 +0100
     1.2 +++ b/deducer.py	Sun Mar 12 17:56:55 2017 +0100
     1.3 @@ -1609,7 +1609,9 @@
     1.4          have_access = self.provider_class_types.has_key(accessor_location)
     1.5  
     1.6          # With an access, attempt to narrow the existing selection of provider
     1.7 -        # types.
     1.8 +        # types. Invocations attempt to find return value information, with
     1.9 +        # instance return values also yielding class providers (since attributes
    1.10 +        # on instances could be provided by classes).
    1.11  
    1.12          if have_access:
    1.13              provider_class_types = self.provider_class_types[accessor_location]
    1.14 @@ -1624,19 +1626,19 @@
    1.15  
    1.16              for access_location in self.alias_index[accessor_location]:
    1.17                  location, name, attrnames, access_number = access_location
    1.18 +                invocation = self.reference_invocations.get(access_location)
    1.19  
    1.20                  # Alias references an attribute access.
    1.21  
    1.22                  if attrnames:
    1.23  
    1.24 -                    # Obtain attribute references for the access.
    1.25 +                    # Obtain references and attribute types for the access.
    1.26  
    1.27                      attrs = self.get_references_for_access(access_location)
    1.28 -
    1.29 -                    # Separate the different attribute types.
    1.30 -
    1.31 -                    (class_types, instance_types, module_types,
    1.32 -                        function_types, var_types) = separate_types(attrs)
    1.33 +                    attrs = self.convert_invocation_providers(attrs, invocation)
    1.34 +
    1.35 +                    (class_types, instance_types, module_types, function_types,
    1.36 +                        var_types) = separate_types(attrs)
    1.37  
    1.38                      # Where non-accessor types are found, do not attempt to refine
    1.39                      # the defined accessor types.
    1.40 @@ -1644,6 +1646,9 @@
    1.41                      if function_types or var_types:
    1.42                          return
    1.43  
    1.44 +                    # Invocations converting class accessors to instances do not
    1.45 +                    # change the nature of class providers.
    1.46 +
    1.47                      class_types = set(provider_class_types).intersection(class_types)
    1.48                      instance_types = set(provider_instance_types).intersection(instance_types)
    1.49                      module_types = set(provider_module_types).intersection(module_types)
    1.50 @@ -1655,8 +1660,10 @@
    1.51  
    1.52                      attr = self.get_initialised_name(access_location)
    1.53                      if attr:
    1.54 -                        (class_types, instance_types, module_types,
    1.55 -                            _function_types, _var_types) = separate_types([attr])
    1.56 +                        attrs = self.convert_invocation_providers([attr], invocation)
    1.57 +
    1.58 +                        (class_types, instance_types, module_types, function_types,
    1.59 +                            var_types) = separate_types(attrs)
    1.60  
    1.61                      # Where no further information is found, do not attempt to
    1.62                      # refine the defined accessor types.
    1.63 @@ -1674,6 +1681,8 @@
    1.64              self.record_reference_types(accessor_location, all_class_types, all_instance_types, all_module_types, False)
    1.65  
    1.66          # Without an access, attempt to identify references for the alias.
    1.67 +        # Invocations convert classes to instances and also attempt to find
    1.68 +        # return value information.
    1.69  
    1.70          else:
    1.71              refs = set()
    1.72 @@ -1686,6 +1695,8 @@
    1.73                      access_location = self.const_accesses[access_location]
    1.74  
    1.75                  location, name, attrnames, access_number = access_location
    1.76 +                invocation = self.reference_invocations.get(access_location)
    1.77 +
    1.78                  attrnames = attrnames and attrnames.split(".")
    1.79                  remaining = attrnames and len(attrnames) > 1
    1.80  
    1.81 @@ -1701,21 +1712,31 @@
    1.82                  attrname = attrnames and attrnames[0]
    1.83  
    1.84                  if attrname:
    1.85 +
    1.86 +                    # Obtain references and attribute types for the access.
    1.87 +
    1.88                      attrs = self.get_references_for_access(access_location)
    1.89 +                    attrs = self.convert_invocations(attrs, invocation)
    1.90                      refs.update(attrs)
    1.91  
    1.92                  # Alias references a name, not an access.
    1.93  
    1.94                  else:
    1.95 +
    1.96 +                    # Obtain initialiser information.
    1.97 +
    1.98                      attr = self.get_initialised_name(access_location)
    1.99 -                    attrs = attr and [attr] or []
   1.100 -                    if not attrs and self.provider_class_types.has_key(access_location):
   1.101 +                    if attr:
   1.102 +                        refs.update(self.convert_invocations([attr], invocation))
   1.103 +
   1.104 +                    # Obtain provider information.
   1.105 +
   1.106 +                    elif self.provider_class_types.has_key(access_location):
   1.107                          class_types = self.provider_class_types[access_location]
   1.108                          instance_types = self.provider_instance_types[access_location]
   1.109                          module_types = self.provider_module_types[access_location]
   1.110 -                        attrs = combine_types(class_types, instance_types, module_types)
   1.111 -                    if attrs:
   1.112 -                        refs.update(attrs)
   1.113 +
   1.114 +                        refs.update(combine_types(class_types, instance_types, module_types))
   1.115  
   1.116              # Record reference details for the alias separately from accessors.
   1.117  
   1.118 @@ -1730,6 +1751,53 @@
   1.119              attrs.append(attr)
   1.120          return attrs
   1.121  
   1.122 +    def convert_invocation_providers(self, refs, invocation):
   1.123 +
   1.124 +        """
   1.125 +        Convert 'refs' to providers corresponding to the results of invoking
   1.126 +        each of the given references, if 'invocation' is set to a true value.
   1.127 +        """
   1.128 +
   1.129 +        if not invocation:
   1.130 +            return refs
   1.131 +
   1.132 +        providers = set()
   1.133 +
   1.134 +        for ref in refs:
   1.135 +            ref = self.convert_invocation_provider(ref)
   1.136 +            if ref.has_kind("<instance>"):
   1.137 +                providers.add(Reference("<class>", ref.get_origin()))
   1.138 +            providers.add(ref)
   1.139 +
   1.140 +        return providers
   1.141 +
   1.142 +    def convert_invocation_provider(self, ref):
   1.143 +
   1.144 +        "Convert 'ref' to a provider appropriate to its invocation result."
   1.145 +
   1.146 +        if ref and ref.has_kind("<class>"):
   1.147 +            return ref
   1.148 +
   1.149 +        return Reference("<var>")
   1.150 +
   1.151 +    def convert_invocations(self, refs, invocation):
   1.152 +
   1.153 +        """
   1.154 +        Convert 'refs' to invocation results if 'invocation' is set to a true
   1.155 +        value.
   1.156 +        """
   1.157 +
   1.158 +        return invocation and map(self.convert_invocation, refs) or refs
   1.159 +
   1.160 +    def convert_invocation(self, ref):
   1.161 +
   1.162 +        "Convert 'ref' to its invocation result."
   1.163 +
   1.164 +        if ref and ref.has_kind("<class>"):
   1.165 +            return ref.instance_of()
   1.166 +
   1.167 +        return Reference("<var>")
   1.168 +
   1.169      def get_initialised_name(self, access_location):
   1.170  
   1.171          """
     2.1 --- a/inspector.py	Sun Mar 12 17:46:28 2017 +0100
     2.2 +++ b/inspector.py	Sun Mar 12 17:56:55 2017 +0100
     2.3 @@ -766,7 +766,7 @@
     2.4          if isinstance(name_ref, ResolvedNameRef) and name_ref.has_kind("<class>"):
     2.5              return InstanceRef(name_ref.reference().instance_of())
     2.6  
     2.7 -        elif isinstance(name_ref, NameRef):
     2.8 +        elif isinstance(name_ref, (NameRef, AccessRef)):
     2.9              return InvocationRef(name_ref)
    2.10  
    2.11          # Provide a general reference to indicate that something is produced
     3.1 --- a/resolving.py	Sun Mar 12 17:46:28 2017 +0100
     3.2 +++ b/resolving.py	Sun Mar 12 17:56:55 2017 +0100
     3.3 @@ -283,13 +283,12 @@
     3.4                          # but they may be resolvable later.
     3.5  
     3.6                          if not ref:
     3.7 -                            if not invocation:
     3.8  
     3.9 -                                # Record the path used for tracking purposes
    3.10 -                                # alongside original name, attribute and access
    3.11 -                                # number details.
    3.12 +                            # Record the path used for tracking purposes
    3.13 +                            # alongside original name, attribute and access
    3.14 +                            # number details.
    3.15  
    3.16 -                                aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
    3.17 +                            aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
    3.18  
    3.19                              continue
    3.20  
    3.21 @@ -303,13 +302,12 @@
    3.22                          # cannot be resolved, but they may be resolvable later.
    3.23  
    3.24                          if not ref:
    3.25 -                            if not invocation:
    3.26  
    3.27 -                                # Record the path used for tracking purposes
    3.28 -                                # alongside original name, attribute and access
    3.29 -                                # number details.
    3.30 +                            # Record the path used for tracking purposes
    3.31 +                            # alongside original name, attribute and access
    3.32 +                            # number details.
    3.33  
    3.34 -                                aliased_names[i] = path, name_ref.name, None, name_ref.number
    3.35 +                            aliased_names[i] = path, name_ref.name, None, name_ref.number
    3.36  
    3.37                              continue
    3.38