Lichen

Changeset

816:c4a8c2683169
2017-06-16 Paul Boddie raw files shortlog changelog graph Permit usage of attributes in mix-in class (and base class) methods that must be provided by descendant classes or their instances.
deducer.py (file) lib/__builtins__/mapping.py (file) lib/__builtins__/sequence.py (file) tests/mixin_only.py (file) translator.py (file)
     1.1 --- a/deducer.py	Mon May 01 23:35:01 2017 +0200
     1.2 +++ b/deducer.py	Fri Jun 16 00:37:50 2017 +0200
     1.3 @@ -562,21 +562,36 @@
     1.4  
     1.5              if not constrained and self.access_index_rev.get(location):
     1.6  
     1.7 -                # Record specific type guard details.
     1.8 -
     1.9 -                if len(all_types) == 1:
    1.10 -                    self.accessor_guard_tests[location] = ("specific", test_label_for_type(first(all_types)))
    1.11 -                elif is_single_class_type(all_types):
    1.12 -                    self.accessor_guard_tests[location] = ("specific", "object")
    1.13 -
    1.14 -                # Record common type guard details.
    1.15 -
    1.16 -                elif len(all_general_types) == 1:
    1.17 -                    self.accessor_guard_tests[location] = ("common", test_label_for_type(first(all_types)))
    1.18 -                elif is_single_class_type(all_general_types):
    1.19 -                    self.accessor_guard_tests[location] = ("common", "object")
    1.20 -
    1.21 -                # Otherwise, no convenient guard can be defined.
    1.22 +                # Test for self parameters in methods that could not be
    1.23 +                # constrained, potentially due to usage unsupported by the class
    1.24 +                # defining the method (such as in mix-in classes).
    1.25 +
    1.26 +                if location.name != "self" or self.in_method(location.path):
    1.27 +                    self.record_guard(location, all_types, all_general_types)
    1.28 +
    1.29 +    def record_guard(self, location, all_types, all_general_types):
    1.30 +
    1.31 +        """
    1.32 +        For the accessor 'location', record a guard if 'all_types' or
    1.33 +        'all_general_types' are appropriate. Where no guard is recorded, access
    1.34 +        tests will need to be applied.
    1.35 +        """
    1.36 +
    1.37 +        # Record specific type guard details.
    1.38 +
    1.39 +        if len(all_types) == 1:
    1.40 +            self.accessor_guard_tests[location] = ("specific", test_label_for_type(first(all_types)))
    1.41 +        elif is_single_class_type(all_types):
    1.42 +            self.accessor_guard_tests[location] = ("specific", "object")
    1.43 +
    1.44 +        # Record common type guard details.
    1.45 +
    1.46 +        elif len(all_general_types) == 1:
    1.47 +            self.accessor_guard_tests[location] = ("common", test_label_for_type(first(all_types)))
    1.48 +        elif is_single_class_type(all_general_types):
    1.49 +            self.accessor_guard_tests[location] = ("common", "object")
    1.50 +
    1.51 +        # Otherwise, no convenient guard can be defined.
    1.52  
    1.53      def classify_accesses(self):
    1.54  
    1.55 @@ -1462,24 +1477,29 @@
    1.56  
    1.57          # Constrain "self" references.
    1.58  
    1.59 -        if location.name == "self":
    1.60 -
    1.61 -            # Test for the class of the method in the deduced types.
    1.62 -
    1.63 -            class_name = self.in_method(location.path)
    1.64 -
    1.65 -            if class_name and class_name not in class_types and class_name not in only_instance_types:
    1.66 +        class_name = self.in_method(location.path)
    1.67 +        constrained = False
    1.68 +
    1.69 +        if class_name and location.name == "self":
    1.70 +
    1.71 +            # Constrain the types to the class's hierarchy.
    1.72 +
    1.73 +            class_types, only_instance_types, module_types, constrained = \
    1.74 +                self.constrain_to_class(class_name, class_types, only_instance_types)
    1.75 +
    1.76 +            # Without any deduced types, produce an error.
    1.77 +
    1.78 +            if not class_types and not only_instance_types:
    1.79                  raise DeduceError("In %s, usage {%s} is not directly supported by class %s or its instances." %
    1.80                                    (location.path, encode_usage(usage), class_name))
    1.81  
    1.82 -            # Constrain the types to the class's hierarchy.
    1.83 -
    1.84 -            t = self.constrain_self_reference(location.path, class_types, only_instance_types)
    1.85 -            if t:
    1.86 -                class_types, only_instance_types, module_types, constrained = t
    1.87 -                return class_types, only_instance_types, module_types, constrained, have_assignments
    1.88 -
    1.89 -        return class_types, only_instance_types, module_types, False, have_assignments
    1.90 +            # If the class defining the method does not appear amongst the
    1.91 +            # types supporting the usage, remove the constrained status of the
    1.92 +            # name.
    1.93 +
    1.94 +            constrained = constrained and (class_name in class_types or class_name in only_instance_types)
    1.95 +
    1.96 +        return class_types, only_instance_types, module_types, constrained, have_assignments
    1.97  
    1.98      def constrain_self_reference(self, unit_path, class_types, only_instance_types):
    1.99  
   1.100 @@ -1496,6 +1516,18 @@
   1.101          if not class_name:
   1.102              return None
   1.103  
   1.104 +        return self.constrain_to_class(class_name, class_types, only_instance_types)
   1.105 +
   1.106 +    def constrain_to_class(self, class_name, class_types, only_instance_types):
   1.107 +
   1.108 +        """
   1.109 +        Constrain to 'class_name' and its descendants, the given 'class_types'
   1.110 +        and 'only_instance_types'.
   1.111 +
   1.112 +        Return the class, instance, module types plus whether the types are
   1.113 +        constrained.
   1.114 +        """
   1.115 +
   1.116          classes = set([class_name])
   1.117          classes.update(self.get_descendants_for_class(class_name))
   1.118  
   1.119 @@ -1512,7 +1544,11 @@
   1.120  
   1.121          "Return whether 'path' refers to a method."
   1.122  
   1.123 -        class_name, method_name = path.rsplit(".", 1)
   1.124 +        t = path.rsplit(".", 1)
   1.125 +        if len(t) == 1:
   1.126 +            return False
   1.127 +
   1.128 +        class_name, method_name = t
   1.129          return class_name != "__builtins__.core.type" and self.importer.classes.has_key(class_name) and class_name
   1.130  
   1.131      def init_reference_details(self, location):
     2.1 --- a/lib/__builtins__/mapping.py	Mon May 01 23:35:01 2017 +0200
     2.2 +++ b/lib/__builtins__/mapping.py	Fri Jun 16 00:37:50 2017 +0200
     2.3 @@ -71,17 +71,6 @@
     2.4  
     2.5          return index % len(self.buckets)
     2.6  
     2.7 -    def _find_entry(self, buckets, key, index):
     2.8 -
     2.9 -        """
    2.10 -        Search in 'buckets' for 'key', using an 'index' identifying the bucket
    2.11 -        involved.
    2.12 -
    2.13 -        Method to be overridden by subclasses.
    2.14 -        """
    2.15 -
    2.16 -        pass
    2.17 -
    2.18      def _items(self):
    2.19  
    2.20          "Return the values stored in all buckets."
    2.21 @@ -103,14 +92,10 @@
    2.22          del self.buckets[index][i]
    2.23          self.size -= 1
    2.24  
    2.25 -    def _resize(self, capacity):
    2.26 +    # Methods implemented by subclasses:
    2.27  
    2.28 -        """
    2.29 -        Resize the hashtable to have the given 'capacity'.
    2.30 -        Method to be overridden by subclasses.
    2.31 -        """
    2.32 -
    2.33 -        pass
    2.34 +    # _find_entry(self, buckets, key, index)
    2.35 +    # _resize(self, capacity)
    2.36  
    2.37      # Public special methods.
    2.38  
     3.1 --- a/lib/__builtins__/sequence.py	Mon May 01 23:35:01 2017 +0200
     3.2 +++ b/lib/__builtins__/sequence.py	Fri Jun 16 00:37:50 2017 +0200
     3.3 @@ -115,26 +115,6 @@
     3.4  
     3.5          return self.__get_multiple_items__(start, end, step)
     3.6  
     3.7 -    # Methods implemented by subclasses.
     3.8 -
     3.9 -    def __setslice__(self, start, end, value):
    3.10 -
    3.11 -        "Method to be overridden by subclasses."
    3.12 -
    3.13 -        pass
    3.14 -
    3.15 -    def __get_single_item__(self, index):
    3.16 -
    3.17 -        "Method to be overridden by subclasses."
    3.18 -
    3.19 -        return None
    3.20 -
    3.21 -    def __set_single_item__(self, index, value):
    3.22 -
    3.23 -        "Method to be overridden by subclasses."
    3.24 -
    3.25 -        pass
    3.26 -
    3.27      def __get_multiple_items__(self, start, end, step):
    3.28  
    3.29          """
    3.30 @@ -150,11 +130,12 @@
    3.31  
    3.32          return result
    3.33  
    3.34 -    def __len__(self):
    3.35 +    # Methods implemented by subclasses:
    3.36  
    3.37 -        "Method to be overridden by subclasses."
    3.38 -
    3.39 -        return 0
    3.40 +    # __setslice__(self, start, end, value)
    3.41 +    # __get_single_item__(self, index)
    3.42 +    # __set_single_item__(self, index, value)
    3.43 +    # __len__(self)
    3.44  
    3.45  class hashable(itemaccess):
    3.46  
    3.47 @@ -270,11 +251,9 @@
    3.48  
    3.49          return not self.__eq__(other)
    3.50  
    3.51 -    def __iter__(self):
    3.52 +    # Methods implemented by subclasses:
    3.53  
    3.54 -        "Method to be overridden by subclasses."
    3.55 -
    3.56 -        raise StopIteration()
    3.57 +    # __iter__(self)
    3.58  
    3.59  def _get_absolute_index(index, length):
    3.60  
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/tests/mixin_only.py	Fri Jun 16 00:37:50 2017 +0200
     4.3 @@ -0,0 +1,33 @@
     4.4 +class MixIn:
     4.5 +    def f(self):
     4.6 +        return self.g()
     4.7 +    def f2(self):
     4.8 +        return self.g2()
     4.9 +
    4.10 +class Concrete(MixIn):
    4.11 +    def g(self):
    4.12 +        return 13579
    4.13 +    def g2(self):
    4.14 +        return 13579
    4.15 +
    4.16 +class Cement(MixIn):
    4.17 +    def g2(self):
    4.18 +        return 24680
    4.19 +
    4.20 +m = MixIn()
    4.21 +
    4.22 +try:
    4.23 +    print m.f()
    4.24 +except TypeError:
    4.25 +    print "m.f: cannot obtain attribute"
    4.26 +
    4.27 +c = Concrete()
    4.28 +print c.f()         # 13579
    4.29 +
    4.30 +try:
    4.31 +    print m.f2()
    4.32 +except TypeError:
    4.33 +    print "m.f2: cannot obtain attribute"
    4.34 +
    4.35 +c2 = Cement()
    4.36 +print c2.f2()       # 24680
     5.1 --- a/translator.py	Mon May 01 23:35:01 2017 +0200
     5.2 +++ b/translator.py	Fri Jun 16 00:37:50 2017 +0200
     5.3 @@ -848,6 +848,12 @@
     5.4          for name in self.importer.function_parameters.get(function_name):
     5.5              self.generate_guard(name)
     5.6  
     5.7 +        # Also support self in methods, since some mix-in methods may only work
     5.8 +        # with certain descendant classes.
     5.9 +
    5.10 +        if self.in_method():
    5.11 +            self.generate_guard("self")
    5.12 +
    5.13          # Produce the body and any additional return statement.
    5.14  
    5.15          expr = self.process_structure_node(n.code) or \