Semantic properties not updated when creating new elements

Hello everyone,

Consider the following model:

package Package {
    part def PartDef {
        attribute attr;
    }
    metadata def partMeta :> Metaobjects::SemanticMetadata {
        :>> baseType = PartDef meta SysML::PartDefinition;
    }

    #partMeta part myPart;
}

I can confirm that myPart.inherited_features indeed contains PartDef::attr. However, when I create a new element with the same annotation, the inherited_features list is not updated:

import syside

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]

    part_def = package.children.elements[0]
    assert isinstance(part_def, syside.PartDefinition)

    part_def_attr = part_def.children.elements[0]
    assert part_def_attr.name == 'attr'

    meta_def = package.children.elements[1]
    assert isinstance(meta_def, syside.MetadataDefinition)

    # Confirm that attr is an inherited feature of myPart
    part_usage = package.children.elements[2]
    assert isinstance(part_usage, syside.PartUsage)

    assert part_def_attr in part_usage.inherited_features

    # Create a new part with the same annotation
    new_part_usage: syside.PartUsage
    _, new_part_usage = package.children.append(syside.OwningMembership, syside.PartUsage)
    new_part_usage.declared_name = 'newPart'

    metadata_usage: syside.MetadataUsage
    _, metadata_usage = new_part_usage.prefixes.append(syside.OwningMembership, syside.MetadataUsage)
    metadata_usage.heritage.insert(0, syside.FeatureTyping, meta_def)

    printer_config = syside.PrinterConfig(line_width=80, tab_width=2)
    printer = syside.ModelPrinter.sysml()
    output = syside.pprint(root_node, printer, printer_config)
    print(output)

    # Check the inherited features --> this assertion fails
    assert part_def_attr in new_part_usage.inherited_features

I didn’t check if this is also the case for other semantic/derived properties, but the user would expected these to be kept updated in my opinion.

Greetings,
Jasper

Hi Jasper,

This is intended because semantic resolution is

  1. incredibly expensive compared to most other languages
  2. and depends on arbitrarily deep and wide hierarchies

So automatic semantic resolution would have to reset and resolve a potentially large chunk of the model either on each modification or lazily by depending on some mutable global state. This is terrible UX, as modifications would no longer be (amortized) constant time and have predictable memory usage, and could even take multiple if not hundreds of milliseconds to resolve compared to the current time of nanoseconds-to-microseconds without semantic resolution. In addition, our preference is to keep the model as a data structure (attributes are typically just graph traversals) and have systems operate on it as needed, i.e. composition over inheritance.

Instead there is an explicit semantic resolution using Sema.resolve and sema_reset.

Hi Daumantas,

I have a follow-up question, consider the following model:

package Package {
    part def A;
    part a : A;
}

And this script:

import syside

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]

    part_def: syside.PartDefinition = package.children.elements[0]
    assert part_def.name == 'A'

    part: syside.PartUsage = package.children.elements[1]
    assert part.name == 'a'

    assert len(list(part.inherited_features)) == 90

    # Add an attribute to the part def
    _, attr = part_def.children.append(syside.OwningMembership, syside.AttributeUsage)
    attr.declared_name = 'myAttribute'

    assert len(list(part.inherited_features)) != 91  # Sema state not resolved

    # Resolve semantics
    syside.sema_reset(part)
    syside.Sema().resolve(model.documents, model.index, model.lib, None)

    assert len(list(part.inherited_features)) == 91

I’m getting the following exception:

    syside.Sema().resolve(model.documents, model.index, model.lib, None)
TypeError: resolve(): incompatible function arguments. The following argument types are supported:
    1. resolve(self, documents: collections.abc.Sequence[syside.core.SharedMutex[syside.core.Document]], index: syside.core.StaticIndex, stdlib: syside.core.Stdlib, reporter: collections.abc.Callable[[syside.core.Document, syside.core.Diagnostic], None] = <nanobind.nb_func object at 0x000002A1111BD900>) -> None

Invoked with types: syside.core.Sema, list, syside.core.StaticIndex, syside.core.Stdlib, NoneType

How can I solve this?

Greetings,
Jasper

Hi Jasper,

Sorry about that, there was an error when attempting to convert the sequence of documents into a native value. Fixed it for the next release, and added an overload that accepts Sequence[Document] instead.

Note: semantics in SysML depend on multiple layers of elements (parents/children) so sema_reset(part) may not be enough, more robust solution is to nuke the whole document instead with sema_reset(part.document).

Additionally,

    # Add an attribute to the part def
    _, attr = part_def.children.append(syside.OwningMembership, syside.AttributeUsage)
    attr.declared_name = 'myAttribute'

    assert len(list(part.inherited_features)) != 91  # Sema state not resolved

does not reset sema on part, that could be too costly, require too much interdependence between the model and sema, and would likely be inaccurate as stated above. Even then, appending an owned feature will not affect inherited_features in any way as owned features are ignored.

Hi Daumantas,

Ok thanks for fixing it in the next release!

Yeah I figured that, it will take some effort to find out exactly how I should reset the semantics when I modify the model, but I think it’s doable.

As you described above, not resetting semantics automatically is because of performance reasons, so that’s completely fine for me: I know when I’m modifying the model (or when I’m done batch-modifying the model), so I can control when to reset/resolve the semantics.

My example though shows adding an attribute to the part def, so it should show up as an inherited feature of the part usage, right? :wink:

Greetings,
Jasper

My example though shows adding an attribute to the part def, so it should show up as an inherited feature of the part usage, right?

According to the spec, owned feature is not an inherited feature as their sets don’t overlap. Inherited features are all the features from base types (.heritage.elements).

The specification requires following multiple layers of constraints, operations, and attributes, but the gist is

features = owned_features ∪ inherited_features
owned_features ∩ inherited_features = ∅
1 Like