Issue evaluating a calculation that returns model elements with Syside Automator

Hello,

I have a question regarding a small test model I created to check whether an element can be retrieved using a calculation.

The calculation logic appears to work in another tool, where it returns the expected model element(s). However, when I evaluate the same calculation using Syside Automator, no element is returned.

As a comparison, I also tested a simple dummy calculation that returns a literal value. That calculation produced the expected output in Syside Automator.

My Model:

package Test{

    part def A;
    connection def B {
        end part [1] :A;
        end part [1] : A;
        connectedTo :SysML::PartUsage   = ConTo(Metaobjects::Metaobject::self meta SysML::ConnectionUsage);
        attribute conTest = ConTest(Metaobjects::Metaobject::self meta SysML::ConnectionUsage);
    }

    part d :A{
        part a:A;
        part b:A;
        part c:A;

        connection b1 : B connect a to b{
            :>>connectedTo;
            attribute :>>conTest;
        }

        connection b2 : B connect b to c{
            :>>connectedTo;
        }

        connection b3: B connect c to a{
            :>>connectedTo;
        }
    }

    calc ConFrom {
        in scope : SysML::ConnectionUsage;
        scope.sourceFeature.featureTarget.? {
            in x;
            x hastype SysML::PartUsage
        }
    }

    calc ConTo {
        in scope : SysML::ConnectionUsage;
        scope.targetFeature.featureTarget.? {in x;x hastype SysML::PartUsage}
        //.targetFeature.featureTarget.featureTarget.? {in x;x hastype SysML::PartUsage}
    }

    calc ConTest {
        in scope : SysML::ConnectionUsage;
        42
    }
}

My automator code:

#for getting the result of connectedTo
from pathlib import Path

import syside

model, diagnostics = syside.load_model(
    paths=[Path("Test.sysml")]
)

# Print model diagnostics correctly
for diagnostic in diagnostics.all:
    print(diagnostic)

# Find Test::d::b1
b1 = next(
    element
    for element in model.nodes(syside.ConnectionUsage)
    if element.name == "b1"
)

print("Found:", b1.name)

# Find B::connectedTo
connected_to = None

for reference in model.nodes(syside.ReferenceUsage):
    owner = reference.owner
    if (
        reference.name == "connectedTo"
        and owner is not None
        and owner.name == "B"
    ):
        connected_to = reference
        break

if connected_to is None:
    raise RuntimeError(
        "Could not find B::connectedTo"
    )

# Get the expression ConTo(...)
expression = connected_to.feature_value_expression

print("Expression:", expression)

if expression is None:
    print("No expression found on B::connectedTo")
else:
    value, report = syside.Compiler().evaluate(
        expression,
        scope=b1,
    )

    if report.fatal:
        print("Calculation evaluation failed:")
        for diagnostic in report.diagnostics:
            print(diagnostic)
    else:
        print("Calculation evaluation succeeded")
        print("connectedTo for b1:", value)
#for getting the result of conTest
from pathlib import Path

import syside

model, diagnostics = syside.load_model(
    paths=[Path("test.sysml")]
)

# Print model diagnostics correctly
for diagnostic in diagnostics.all:
    print(diagnostic)

# Find Test::d::b1
b1 = next(
    element
    for element in model.nodes(syside.ConnectionUsage)
    if element.name == "b1"
)

print("Found:", b1.name)

# Find B::conTest
connected_to = None

for attribute in model.nodes(syside.AttributeUsage):
    owner = attribute.owner
    if (
        attribute.name == "conTest"
        and owner is not None
        and owner.name == "B"
    ):
        connected_to = attribute
        break

if connected_to is None:
    raise RuntimeError(
        "Could not find B::conTest"
    )

# Get the expression ConTest(...)
expression = connected_to.feature_value_expression
print("Expression:", expression)

if expression is None:
    print("No expression found on B::conTest")
else:
    value, report = syside.Compiler().evaluate(
        expression,
        scope=b1,
    )

    if report.fatal:
        print("Calculation evaluation failed:")
        for diagnostic in report.diagnostics:
            print(diagnostic)
    else:
        print("Calculation evaluation succeeded")
        print("conTest for b1:", value)

The result I got when I ran the automator :

Found: b1
Expression: Test::b::connectedTo::(anonymous InvocationExpression)
Calculation evaluation failed:
:34:9: error (unsupported-type): SelectExpression compilation is not supported // When I tried to get the value inside the reference usage connectedTo

Found: b1
Expression: Test::b::conTest::(anonymous InvocationExpression)
Calculation evaluation succeeded
conTest for b1: 42 //When I tried to get the value of the attribute usage conTest

Could someone please clarify whether this could be a current limitation of Syside Automator’s calculation/expression evaluation, or whether there may be an issue with my modeling or evaluation approach?

Thank you very much for your help.

Best regards,
AK

Hi,

Reflection is finicky, because it is supposed to evaluate the exact reference that is declared. In this case Metaobjects::Metaobject::self meta SysML::ConnectionUsage evaluates to an empty sequence because the referenced self is a plain Feature, not a ConnectionUsage. You can see this behaviour from

a = b meta c;

with CST

Namespace [0, 0] - [1, 0]
  children: OwningMembership [0, 0] - [0, 13]
    target: ReferenceUsage [0, 0] - [0, 13]
      declared_name: NAME [0, 0] - [0, 1]
      feature_value: FeatureValue [0, 2] - [0, 12]
        = [0, 2] - [0, 3]
        target: OperatorExpression [0, 4] - [0, 12]
          children: ParameterMembership [0, 4] - [0, 5]
            target: Feature [0, 4] - [0, 5]
              feature_value: FeatureValue [0, 4] - [0, 5]
                target: MetadataAccessExpression [0, 4] - [0, 5]  |
                  referenced_element: Membership [0, 4] - [0, 5]  | b
                    target: ElementReference [0, 4] - [0, 5]      |
                      segment: NAME [0, 4] - [0, 5]               |
          operator: meta [0, 6] - [0, 10]
          children: ReturnParameterMembership [0, 11] - [0, 12]
            target: Feature [0, 11] - [0, 12]
              heritage: FeatureTyping [0, 11] - [0, 12]
                target: TypeReference [0, 11] - [0, 12]
                  segment: NAME [0, 11] - [0, 12]
      ; [0, 12] - [0, 13]

MetadataAccessExpression operates on the referenced element directly without going through evaluation loop. I am not aware of how to get the dynamic metadata of the scope element otherwise.

Secondly, select and collect expressions are not supported in v0.10.3.

Perhaps, a better approach would be to allow registering builtin functions dynamically from the API so they would not have any syntax constraints.

Hello,
Thank you very much for the detailed explanation. This clarifies why the expression returns an empty sequence and why the calculation cannot currently be evaluated by Syside Automator.

Best regards,

AK