Redefinition referencing behavior

Hi everyone,

Consider the following model:

package Package {
    part def BasePart {
        attribute attr1 default 5.0;
        attribute attr2 default 42.0;
    }
    part def SpecialPart :> BasePart {
        attribute :>> attr1 = 6.0;
    }

    part def Container {
        part thePartUsage : BasePart;
    }
    part def SpecialContainer :> Container {
        part :>> thePartUsage : SpecialPart;
        attribute attrRef = thePartUsage.attr1;
    }
}

What I would expect is SpecialContainer::thePartUsage to have 2 features: SpecialPart::attr1 and BasePart::attr2, and therefore that thePartUsage.attr1 refers to SpecialPart::attr1.

However, this seems to not be the case (I’m using Syside Automator 0.11.0rc1):

import syside

def _print_features(element: syside.Type, indent=''):
    if indent == '':
        print(f'{indent}Element: {element.qualified_name}')
    for feature in element.features.collect():
        if not feature.name or feature.is_library_element:
            continue

        is_owned = feature.owner == element

        value = feature.feature_value_expression
        if value is not None:
            if isinstance(value, syside.LiteralRational):
                value = value.value
            elif isinstance(value, syside.FeatureChainExpression):
                value = value.target_feature.qualified_name
            else:
                raise NotImplementedError(repr(value))
        value = f' = {value}' if value else ''

        print(f'{indent}  {feature.qualified_name}{value} (owned feature = {is_owned})')

        if isinstance(feature, syside.PartUsage):
            _print_features(feature, indent+'  ')

model, _ = syside.load_model(['model.sysml'])
with model.documents[-1].lock() as doc:
    root_node = doc.root_node
    package: syside.Package = root_node.children.elements[0]
    for child_el in package.children.elements:
        _print_features(child_el)

Output:

Element: Package::BasePart
  Package::BasePart::attr1 = 5.0 (owned feature = True)
  Package::BasePart::attr2 = 42.0 (owned feature = True)
Element: Package::SpecialPart
  Package::SpecialPart::attr1 = 6.0 (owned feature = True)
  Package::BasePart::attr2 = 42.0 (owned feature = False)
Element: Package::Container
  Package::Container::thePartUsage (owned feature = True)
    Package::BasePart::attr1 = 5.0 (owned feature = False)
    Package::BasePart::attr2 = 42.0 (owned feature = False)
Element: Package::SpecialContainer
  Package::SpecialContainer::thePartUsage (owned feature = True)
    Package::BasePart::attr1 = 5.0 (owned feature = False)
    Package::BasePart::attr2 = 42.0 (owned feature = False)
    Package::SpecialPart::attr1 = 6.0 (owned feature = False)
  Package::SpecialContainer::attrRef = Package::BasePart::attr1 (owned feature = True)

As you can see, the final thePartUsage has 3 feature, and the attrRef points to BasePart::attr1, not the redefined one…

This could be due to the fact that the the redefinition (part :>> thePartUsage : SpecialPart;) actually defines 2 inheritance “streams” (the redefinition and the feature typing), and when “merging” the two feature inheritance streams it is not taking redefinition into account.

What should be the expected behavior here? Is SysML v2 not precise enough in this case, or is this a parser bug/limitation?

Cheers,
Jasper

Hi,

This is known issue/limitation as Syside uses strictly DFS for name lookup and stops on the first successful match. Extending lookup to continue searching for potentially better redefined matches has significant effect on performance.

Ideally, SysML would define a clear resolution order that is also cheap to compute so that this multiple inheritance annoyance can be dealt with without having to waste resources scanning extremely deep hierarchies fully. E.g. search types first, then subsettings.

Hi Daumantas,

I understand, but I do need to correctly deal with redefinition in my current project… So that means before I can loop over the features of a given element, I first need to filter out features that are redefined by other features. Since there is no .specializes() equivalent just for redefinition (or is there?), I therefore need to loop over the heritage of every element and compare that with the other features. Sounds faster if that could be done internally by Syside Automator with some helper function than doing it in Python…

Maybe an idea for a helper function in a future version? :slight_smile:

Greetings,
Jasper

Sure, it needs to run in a second pass over collected elements because some later features may redefine earlier ones, that is it does not work with lazy generation. This should work in the meantime:

def discard_redefined(elements: Iterable[syside.Element]) -> list[syside.Element]:
    redefined = set[syside.Element]()
    out: list[syside.Element] = list(elements)
    queue: list[syside.Feature] = []

    for element in out:
        feat = element.try_cast(syside.Feature)
        if not feat:
            continue

        queue.clear()
        queue.append(feat)

        while queue:
            top = queue.pop(0)
            for redefinition in top.owned_redefinitions.collect():
                if target := redefinition.redefined_feature:
                    hidden = target.feature_target
                    if hidden in redefined:
                        continue

                    queue.append(hidden)
                    redefined.add(hidden)

    return [element for element in out if element not in redefined]

In Syside v0.11 there may be!

With Syside v0.11 we are introducing syside.query module that contains higher level API, and one function specializations could probably be used to do what you want.

You can get the pre-release to try out as described here: Syside v0.11.0-rc.1: try out new features earlier

I don’t recommend relying for this in production yet, as we have found some bugs in the pre-release that will be fixed for the final 0.11.0, so we don’t want you to accidentally become reliant on some of those hidden bugs.

Ohhh that is very nice!
I have a lot of code that can be improved by the syside.query module in general it looks like :slight_smile: