Your instinct to reach for perform is the right one — use perform action2 and perform action3, not the bare action2;.
-
Writing action2; inside the branch does not refer to your existing action2. It creates a brand-new reference named action2, local to the branch —
not typed as an action, and resolving to nothing (SySIDE leaves its referenced_feature empty). So it neither performs your action2 nor receives the flow action1.data to action2.data;;
that flow still targets the outer action2. perform action2 is a genuine reference — its performed action is the action2 you declared, so the flow’s target and the performed step are
the same element. Both forms load without error, so the difference is silent, which is what makes the bare form easy to get wrong.
-
No extra binding needed. Because perform action2 is the same action2 your flow targets, flow action1.data to action2.data; already supplies its in data. You’d
only add an explicit binding if no flow carried that value.
-
Write the first action directly and chain the rest with then — don’t open the branch with first:
if condition {
perform action2;
then perform action3;
}
first only marks the start step at the top of the body (your first start;); inside a branch it’s a parse error.
Checking what an action reference points at
Both action2; and perform action2; load without an error, so the editor
won’t tell you which one actually connects to your declared action2 (the one
your flow feeds). Here’s how to see what SySIDE really linked.
Note: this uses a small Python script. If you’re comfortable running
Python, it gives you a direct look at the resolved model.
The script
Save as show_model.py and run uv run show_model.py yourfile.sysml:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["syside"]
# ///
import sys, syside
model, _ = syside.try_load_model(sysml_source=open(sys.argv[1]).read())
with list(model.user_docs)[0].lock() as locked:
print(syside.sexp(locked.root_node, include_implicit=False, print_references=True))
It prints the model as a nested tree. print_references=True adds a
... to <name> line under each reference, showing what it resolved to.
What it shows
With the bare name, the branch step has no to ... line:
(ActionUsage thenClause
(FeatureMembership
(ReferenceUsage action2))) <-- resolves to nothing
With perform, it carries a ReferenceSubsetting to ...::action2:
(ActionUsage thenClause
(FeatureMembership
(PerformActionUsage action2
(ReferenceSubsetting to Test::useCaseUsage::action2)))) <-- your action2
(For comparison, your flow’s target end always shows
ReferenceSubsetting to Test::useCaseUsage::action2 — so with perform the
flow target and the performed step are the same element; with the bare name
they are not.)