Get rid of type origins

Due to how versioning works, when class inheritance
graph for particular class might be changed so that
there won't be several versions of the same class along
different inheritance paths if they all can be merged into
a single class without breaking the requirements.
This means that parent of some base class might be different
from those that would be if that base class would created alone.

Before this commit there was a class remapping table that
was saying which parent class should be replaced with which
in the inheritance graph (considering that both classes differ
in version only). Each class used to have such table. Thus in
oder to do the graph traversal type origin (root of that graph)
need to be known in order to obtain remappings table.

However in certain cases information on type origin got lost.
For example if one would do cast($obj, Parent).method()
the method would be looked up in the Parent class and all
in ancestors like if the Parent was the type origin. This could
cause different resolution from what would happen if the lookup
algorithm new that the true type origin is type($obj) instead.

This commit brings different solution to the original problem
eliminating the need to know type origin. Instead when the base
class parents need to be changed, because we cannot change
the class parents due to the fact that this change is only
relevant in context of origin type, we create a clone MuranoType
instance which differs in its parents ony.

The downside of this solution is that now there could be several
MuranoType instances representing the same type and attempt
to lookup the type in its package may return different object
from what you would get from type(cast($obj, Parent)). This is
solved by making them comparable (with == and !=). So now
instead of saying "obj.type is Type" one should use
"obj.type == Type" instead. Because there is no "is" comparision
in MuranoPL nothing breaks there from this change.

Another issue is that there are static properties which values
must be shared across all type clones. This is solved by making
a clone with copy.copy() shallow cloning function. Thus internal
dictionary where property values are stored remains the same
for all clones.

Closes-Bug: #1608712
Change-Id: Ib29f731f2598eaf7e7ea5f69f75e023d7155af5e
This commit is contained in:
Stan Lagun 2016-08-01 22:53:37 -07:00
parent a4c8f3bb56
commit d6456f00d2
4 changed files with 57 additions and 32 deletions

View File

@ -87,18 +87,18 @@ def merge_providers(initial_class, producer, context):
for item in meta:
cardinality = item.type.cardinality
inherited = item.type.inherited
if cls is not initial_class and (
if cls != initial_class and (
not inherited or item.type in skip_list):
continue
if cardinality == dsl_types.MetaCardinality.One:
cls_skip_list.add(item.type)
all_meta.append((cls, item))
all_meta.extend(merger(cls.parents(initial_class), cls_skip_list))
all_meta.extend(merger(cls.parents, cls_skip_list))
meta_types = {}
for cls, item in all_meta:
entry = meta_types.get(item.type)
if entry is not None:
if entry is not cls:
if entry != cls:
raise ValueError(
u'Found more than one instance of Meta {} '
u'with Cardinality One'.format(item.type.name))

View File

@ -41,7 +41,7 @@ class MuranoObject(dsl_types.MuranoObject):
if not isinstance(self.__config, dict):
self.__config = {}
known_classes[murano_class.name] = self
for parent_class in murano_class.parents(self.real_this.type):
for parent_class in murano_class.parents:
name = parent_class.name
if name not in known_classes:
obj = MuranoObject(
@ -196,6 +196,8 @@ class MuranoObject(dsl_types.MuranoObject):
name, self.__type)
def set_property(self, name, value, context=None):
if self is not self.real_this:
return self.real_this.set_property(name, value, context)
start_type, derived = self.__type, False
caller_class = None if not context else helpers.get_type(context)
if caller_class is not None and caller_class.is_compatible(self):
@ -240,7 +242,7 @@ class MuranoObject(dsl_types.MuranoObject):
def cast(self, cls):
for p in helpers.traverse(self, lambda t: t.__parents.values()):
if p.type is cls:
if p.type == cls:
return p
raise TypeError('Cannot cast {0} to {1}'.format(self.type, cls))

View File

@ -14,6 +14,7 @@
import abc
import collections
import copy
import weakref
import semantic_version
@ -83,20 +84,57 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
raise exceptions.InvalidInheritanceError(
u'Type {0} cannot have parent with Usage {1}'.format(
self.name, p.usage))
remappings = self._build_parent_remappings()
self._parents = self._adjusted_parents(remappings)
self._context = None
self._exported_context = None
self._parent_mappings = self._build_parent_remappings()
self._property_values = {}
self._meta = dslmeta.MetaData(meta, dsl_types.MetaTargets.Type, self)
self._meta_values = None
self._imports = list(self._resolve_imports(imports))
def _adjusted_parents(self, remappings):
seen = {}
def altered_clone(class_):
seen_class = seen.get(class_)
if seen_class is not None:
return seen_class
cls_remapping = remappings.get(class_)
if cls_remapping is not None:
return altered_clone(cls_remapping)
new_parents = [altered_clone(p) for p in class_._parents]
if all(a is b for a, b in zip(class_._parents, new_parents)):
return class_
res = copy.copy(class_)
res._parents = new_parents
res._meta_values = None
res._context = None
res._exported_context = None
seen[class_] = res
return res
return [altered_clone(p) for p in self._parents]
def __eq__(self, other):
if not isinstance(other, MuranoType):
return False
return self.name == other.name and self.version == other.version
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash((self.name, self.version))
@property
def usage(self):
return dsl_types.ClassUsages.Class
@property
def declared_parents(self):
def parents(self):
return self._parents
@property
@ -110,10 +148,6 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
names.update(c.methods.keys())
return tuple(names)
@property
def parent_mappings(self):
return self._parent_mappings
@property
def extension_class(self):
return self._extension_class
@ -147,14 +181,14 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
raise TypeError('property_typespec')
self._properties[property_typespec.name] = property_typespec
def _find_symbol_chains(self, func, origin):
def _find_symbol_chains(self, func):
queue = collections.deque([(self, ())])
while queue:
cls, path = queue.popleft()
symbol = func(cls)
segment = (symbol,) if symbol is not None else ()
leaf = True
for p in cls.parents(origin):
for p in cls.parents:
leaf = False
queue.append((p, path + segment))
if leaf:
@ -176,7 +210,7 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
def _choose_symbol(self, func):
chains = sorted(
self._find_symbol_chains(func, self),
self._find_symbol_chains(func),
key=lambda t: len(t))
result = []
for i in range(len(chains)):
@ -270,9 +304,9 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
dsl.MuranoObjectInterface,
dsl_types.MuranoTypeReference)):
obj = obj.type
if obj is self:
if obj == self:
return True
return any(cls is self for cls in obj.ancestors())
return any(cls == self for cls in obj.ancestors())
def __repr__(self):
return 'MuranoClass({0}/{1})'.format(self.name, self.version)
@ -305,7 +339,7 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
}
for cls, parent in helpers.traverse(
((self, parent) for parent in self._parents),
lambda cp: ((cp[1], anc) for anc in cp[1].declared_parents)):
lambda cp: ((cp[1], anc) for anc in cp[1].parents)):
if cls.package != parent.package:
requirement = cls.package.requirements[parent.package.name]
aggregation.setdefault(parent.package.name, set()).add(
@ -316,8 +350,7 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
mappings = self._remap_package(versions)
package_bindings.update(mappings)
for cls in helpers.traverse(
self.declared_parents, lambda c: c.declared_parents):
for cls in helpers.traverse(self.parents, lambda c: c.parents):
if cls.package in package_bindings:
package2 = package_bindings[cls.package]
cls2 = package2.classes[cls.name]
@ -350,17 +383,8 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
i += 1
return result
def parents(self, origin):
mappings = origin.parent_mappings
yielded = set()
for p in self._parents:
parent = mappings.get(p, p)
if parent not in yielded:
yielded.add(parent)
yield parent
def ancestors(self):
for c in helpers.traverse(self, lambda t: t.parents(self)):
for c in helpers.traverse(self, lambda t: t.parents):
if c is not self:
yield c
@ -553,6 +577,6 @@ def weigh_type_hierarchy(cls):
result = {}
for c, w in helpers.traverse(
[(cls, 0)], lambda t: six.moves.map(
lambda p: (p, t[1] + 1), t[0].parents(cls))):
lambda p: (p, t[1] + 1), t[0].parents)):
result.setdefault(c.name, w)
return result

View File

@ -90,8 +90,7 @@ def new_from_model(context, model, owner=None):
def super_(context, object_, func=None):
cast_type = helpers.get_type(context)
if func is None:
return [object_.cast(type) for type in cast_type.parents(
object_.real_this.type)]
return [object_.cast(type) for type in cast_type.parents]
return six.moves.map(func, super_(context, object_))