From d178d2b9d4730cc0099f87d32b7166f584e70c4a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 22:34:29 +0000 Subject: [PATCH 1/4] fix(migrate): pass the uniqueness argument the read result pin's shaped multiplicity takes Co-Authored-By: jason.han --- internal/translate/migrate/activity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/translate/migrate/activity.go b/internal/translate/migrate/activity.go index 6562d60e57..9b401b8b15 100644 --- a/internal/translate/migrate/activity.go +++ b/internal/translate/migrate/activity.go @@ -2338,7 +2338,7 @@ func (a *activity) readFeature(n *sysmlv1.Element, name string) { } a.names[r] = pname mult, mnote := a.m.multiplicity(r) - a.m.w.line("out " + writeName(pname) + shaped(mult, r, true) + " = " + expr + ";") + a.m.w.line("out " + writeName(pname) + shaped(mult, r, true, false) + " = " + expr + ";") a.m.add(r, verdictFor(mnote), a.m.v2Name(n)+"."+pname, mnote) a.m.add(n, Mapped, name, "") }) From 06a235fe1270f613100752644213cf51f7d26d42 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 23:04:20 +0000 Subject: [PATCH 2/4] fix(migrate): count only written sends as senders, note UI-posted specializations, keep a misrouted send's argument note, and name only written clock waits A send action refused as a placeholder performs nothing, so it no longer counts as a sender of its signal: an accept of a signal only the tool's UI prototype posts is noted once every send is written. A button posting a signal specializing the accepted one raises the same note, naming the specialization. A send on a port that is not the sender's keeps its argument note beside the port note. An entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes: a duration constraint written as a wait, a time event whose time is spelled, or a called behavior that has a v2 declaration. Co-Authored-By: jason.han --- .../migrate-sender-and-wait-notes.fixed.md | 1 + docs/reference/sysml-v1-migration.md | 4 +- internal/translate/migrate/activity.go | 64 +++++--- internal/translate/migrate/migrate.go | 3 +- internal/translate/migrate/senders.go | 152 +++++++++++++----- internal/translate/migrate/states.go | 2 +- tests/migrate/instant_waits_test.go | 12 +- tests/migrate/signal_absent_argument_test.go | 42 +++++ .../xmi/instant_waits.golden.report.txt | 26 ++- .../testdata/xmi/instant_waits.golden.sysml | 20 +++ tests/migrate/testdata/xmi/instant_waits.xmi | 30 ++++ .../testdata/xmi/ui_stimuli.golden.report.txt | 22 ++- .../testdata/xmi/ui_stimuli.golden.sysml | 13 +- tests/migrate/testdata/xmi/ui_stimuli.xmi | 30 +++- tests/migrate/ui_stimuli_test.go | 7 +- 15 files changed, 351 insertions(+), 77 deletions(-) create mode 100644 changes/unreleased/migrate-sender-and-wait-notes.fixed.md diff --git a/changes/unreleased/migrate-sender-and-wait-notes.fixed.md b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md new file mode 100644 index 0000000000..3dfd715ff0 --- /dev/null +++ b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md @@ -0,0 +1 @@ +- **The migrator's ledger counts a send action as a sender only once it is written, and a button of the tool's UI prototype as posting the signals it specializes.** An accept of a signal is noted as waiting for a message only the tool's UI posts when no written send sends it or a signal specializing it — a send left as a placeholder, which performs nothing, no longer hides the note — and a button posting a specialization of the accepted signal now raises it. A send whose port is not the sender's keeps the note on its arguments beside the one on its port, and an entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes, not for a time event or duration constraint it leaves as a placeholder. diff --git a/docs/reference/sysml-v1-migration.md b/docs/reference/sysml-v1-migration.md index 5e83d38e66..7f2d167f45 100644 --- a/docs/reference/sysml-v1-migration.md +++ b/docs/reference/sysml-v1-migration.md @@ -255,7 +255,7 @@ returned over the service yet. | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity with no nodes | an empty action: `entry action x;` in a state, `do action x { }` on a transition, whose target follows on the next line | mapped (the note says the action is empty) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity whose every action node is refused | the action, holding the flow and a comment for each refused node; the behavior runs nothing | approximated (each node: **unmapped**) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an OpaqueBehavior in a language the mapping cannot write | the action, holding the body as a comment | approximated | -| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes, an accept of a time event, or a call of an activity, or of an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted) | approximated | +| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes that is written as a wait, an accept of a time event whose time is written, or a call of an activity, or of an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted; a constraint or time event left as a placeholder is no wait, and is not noted either) | approximated | | Transition `effect` referring to a behavior owned elsewhere | `do action : Def` on the transition, the target following on the next line; the behavior's own `action def` is written once where it is owned | mapped | | `entry`, `doActivity`, `exit` behavior or transition `effect` referring to a behavior that is not written, or is written as something no state runs (a StateMachine, for one) | comment in the state's body or before the transition (a `/* */` comment is admitted only where a member may appear, not between the transition's clauses); the state or transition is written without it | approximated (the state or transition: "its … is not run"; a behavior not written: **unmapped**) | | Transition `effect` with `in` parameters | the accepted signal is named, `accept sig : Sig`, and each parameter typed by the signal (or a general of it), or the sole untyped one, is bound to it: `in p : Sig = sig;`; a parameter of another type takes no value | mapped (an unbound parameter: approximated) | @@ -272,7 +272,7 @@ returned over the service yet. | State `stateInvariant` | comment in the state's body quoting the constraint; the state is written with a body so the comment has a place | **unmapped** — no v2 form | | Initial transition with a trigger or guard | the region's `entry; then s;`; each trigger and the guard are dropped and reported apart from the transition | approximated (the trigger, the guard: unmapped) | | SignalEvent, ChangeEvent, relative TimeEvent | written where a trigger refers to them, as `accept Sig`, `accept when `, `accept after [SI::s]` | mapped / approximated | -| SignalEvent whose signal no send or broadcast action of the document sends (neither it nor a signal specializing it, which the accept takes too), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated | +| SignalEvent whose signal no written send action of the document sends (neither it nor a signal specializing it, which the accept takes too; a send left as a placeholder performs nothing and is no sender), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal, or a signal specializing it, as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated | | Absolute TimeEvent a trigger refers to | `accept at ` on the transition or accept action, the instant an attribute of the `state def`/`action def` typed `Time::TimeInstantValue` when `when` is a number with a time unit or an expression that resolves, read on the simulation clock, which starts at 0 | approximated (the clock's origin is the run's, not the calendar's) | | Event (of any kind) no trigger refers to | — | skipped, counted as a model element nothing refers to | | SignalEvent whose signal is not written, TimeEvent whose `when` is not a number with a time unit | comment; the transition that refers to it drops the trigger | **unmapped** — the reason names the signal or the time | diff --git a/internal/translate/migrate/activity.go b/internal/translate/migrate/activity.go index 9b401b8b15..b4feb4b2d1 100644 --- a/internal/translate/migrate/activity.go +++ b/internal/translate/migrate/activity.go @@ -840,24 +840,53 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { if len(dcs) == 0 { return "", false } - dc := dcs[0] for _, other := range dcs[1:] { - a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dc)+" is written as its wait") + a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dcs[0])+" is written as its wait") + } + w := a.m.waitOf(e) + switch { + case w.ok: + a.m.add(w.dc, Approximated, a.m.v2Name(a.def), w.note) + return w.expr, true + case w.noInterval: + a.m.unmapped(w.dc, w.why) + default: + a.unmappedWait(w.dc, e, w.why) + } + return "", false +} + +// wait is what the duration constraint dc on an element is written as: the +// delay before the element in seconds with the note on it when ok, else why +// none is written — the constraint has no interval, or its bounds admit no one wait. +type wait struct { + dc *sysmlv1.Element + expr, note string + why string + ok bool + noInterval bool +} + +// waitOf decides the wait the first duration constraint on e is written as; a +// zero wait when e has none. The decision is shared by the writer and by the +// search for the clock waits of a behavior performed at an instant. +func (m *migration) waitOf(e *sysmlv1.Element) wait { + dcs := m.bounded[e] + if len(dcs) == 0 { + return wait{} } + dc := dcs[0] spec := firstOwned(dc, "specification") if spec == nil || spec.Type != "DurationInterval" && spec.Type != "Interval" { - a.m.unmapped(dc, "the duration constraint has no interval") - return "", false + return wait{dc: dc, why: "the duration constraint has no interval", noInterval: true} } - lo, lok, lnote := a.m.durationExpr(a.m.model.Ref(spec, "min"), e) - hi, hok, hnote := a.m.durationExpr(a.m.model.Ref(spec, "max"), e) - if bound, bnote, ok := a.m.singleValue(spec, lo, lok, hok); ok { - a.m.add(dc, Approximated, a.m.v2Name(a.def), joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e))) - return inSeconds(bound), true + lo, lok, lnote := m.durationExpr(m.model.Ref(spec, "min"), e) + hi, hok, hnote := m.durationExpr(m.model.Ref(spec, "max"), e) + if bound, bnote, ok := m.singleValue(spec, lo, lok, hok); ok { + return wait{dc: dc, expr: inSeconds(bound), note: joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e)), ok: true} } if !lok || !hok { - a.unmappedWait(dc, e, a.m.openInterval(spec, lo, lok, lnote, hi, hok, hnote)) - return "", false + return wait{dc: dc, why: m.openInterval(spec, lo, lok, lnote, hi, hok, hnote)} } note := joinNotes(lnote, hnote) var expr string @@ -865,8 +894,7 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { hf, herr := strconv.ParseFloat(hi, 64) switch { case lerr == nil && herr == nil && lf > hf: - a.unmappedWait(dc, e, "the interval's min "+lo+" exceeds its max "+hi) - return "", false + return wait{dc: dc, why: "the interval's min " + lo + " exceeds its max " + hi} case lo == hi: expr = lo note = joinNotes(note, "written as a fixed wait of "+lo+" s before "+describe(e)) @@ -874,8 +902,7 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { expr = "RandomFunctions::uniform(" + lo + ", " + hi + ")" note = joinNotes(note, "written as a wait drawn uniformly over ["+lo+", "+hi+"] s before "+describe(e)+"; a tool's fixed min or max mode is a run setting, not the model's") } - a.m.add(dc, Approximated, a.m.v2Name(a.def), note) - return inSeconds(expr), true + return wait{dc: dc, expr: inSeconds(expr), note: note, ok: true} } func (a *activity) unmappedWait(dc, e *sysmlv1.Element, note string) { @@ -2405,11 +2432,11 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) { path, hasPath := a.portPath(port) switch { case !a.m.written(port): - note = "the port " + qualifiedName(port) + " has no v2 declaration; the signal is sent to the sender" + note = joinNotes(note, "the port "+qualifiedName(port)+" has no v2 declaration; the signal is sent to the sender") case hasPath: line += " via " + a.m.respellThis(a.self()+"."+path, a.act) default: - note = "the port " + qualifiedName(port) + " is no port of the object the sender acts on; the signal is sent to the sender" + note = joinNotes(note, "the port "+qualifiedName(port)+" is no port of the object the sender acts on; the signal is sent to the sender") } } else if t := firstOwned(n, "target"); t != nil { obj, _, ok := a.objectOf(t) @@ -2424,6 +2451,7 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) { } } a.m.w.line(line + ";") + a.m.senders.sent[sig] = true }) a.m.add(n, verdictFor(note), name, note) } @@ -2486,7 +2514,7 @@ func (a *activity) trigger(t, n *sysmlv1.Element) (clause, note string, ok bool) tnote := "" if sig := a.m.model.Ref(ev, "signal"); ev.Type == "SignalEvent" && sig != nil { clause, tnote = a.m.actionRoute(clause, t, a.selfType(), a.act, a.viaPrefix(), sig) - tnote = joinNotes(tnote, a.m.unsentNote(sig)) + a.m.uiAccept(sig, t, n) if len(n.Owned("result")) > 0 { a.payload[n.Owned("result")[0]] = sig } diff --git a/internal/translate/migrate/migrate.go b/internal/translate/migrate/migrate.go index 35bd4db2e7..b3490170b3 100644 --- a/internal/translate/migrate/migrate.go +++ b/internal/translate/migrate/migrate.go @@ -157,7 +157,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { opUsage: map[*sysmlv1.Element]string{}, deciding: map[*sysmlv1.Element]bool{}, bounded: map[*sysmlv1.Element][]*sysmlv1.Element{}, - senders: senders{actions: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}}, + senders: senders{sent: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}}, allocated: map[*sysmlv1.Element][]*sysmlv1.Element{}, triggered: map[*sysmlv1.Element]bool{}, snapshots: map[*sysmlv1.Element]snapshotTyping{}, @@ -259,6 +259,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { m.views(nil) m.flushFlows() m.placeholderEnds() + m.uiOnlyAccepts() m.unwrittenEvents() m.w.fill() m.diagrams() diff --git a/internal/translate/migrate/senders.go b/internal/translate/migrate/senders.go index 8037a8cd35..a9fa8e1646 100644 --- a/internal/translate/migrate/senders.go +++ b/internal/translate/migrate/senders.go @@ -1,27 +1,33 @@ package migrate import ( + "sort" "strings" "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" ) -// senders indexes what posts each signal: the send and broadcast actions of the -// model, and the signal instances of the tool's UI prototype, which a button -// posts when it is pressed in the tool and the migration does not write. +// senders indexes what posts each signal: the send actions the migration +// wrote, and the signal instances of the tool's UI prototype, which a button +// posts when it is pressed in the tool and the migration does not write. The +// accepts of a signal a button posts are noted once every send is written, since +// a send action is a sender only when it is written, not refused as a placeholder. type senders struct { - actions map[*sysmlv1.Element]bool + sent map[*sysmlv1.Element]bool buttons map[*sysmlv1.Element]int + accepts []uiAccept } -// recordSender notes e as a sender of a signal, when it is one. +// uiAccept is an accept of sig: the elements reported for it, which are noted +// when no send action of the document turns out to send sig. +type uiAccept struct { + sig *sysmlv1.Element + of []*sysmlv1.Element +} + +// recordSender notes e as a button of the tool's UI prototype posting a signal, +// when it is one. func (m *migration) recordSender(e *sysmlv1.Element) { - switch e.Type { - case "SendSignalAction", "BroadcastSignalAction": - if sig := m.model.Ref(e, "signal"); sig != nil { - m.senders.actions[sig] = true - } - } for _, s := range e.Stereotypes { if !simulationProvenance.applies(s, "SignalInstance") { continue @@ -34,26 +40,75 @@ func (m *migration) recordSender(e *sysmlv1.Element) { } } +// uiAccept records that the elements of are an accept of sig, to be noted by +// uiOnlyAccepts when a button of the tool's UI prototype posts sig or a signal +// specializing it and no send action is written for either. +func (m *migration) uiAccept(sig *sysmlv1.Element, of ...*sysmlv1.Element) { + if sig == nil { + return + } + if n, _ := m.buttonsPosting(sig); n > 0 { + m.senders.accepts = append(m.senders.accepts, uiAccept{sig: sig, of: of}) + } +} + +// uiOnlyAccepts notes, once every send action is written, each recorded accept +// whose signal only the tool's UI prototype posts. +func (m *migration) uiOnlyAccepts() { + for _, acc := range m.senders.accepts { + if m.sentByAction(acc.sig) { + continue + } + note := m.unsentNote(acc.sig) + for _, e := range acc.of { + m.annotate(e, note, true) + } + } +} + // unsentNote says that the tool's UI prototype is what posts sig: no send action -// of the document sends it, only a button of the prototype, which the migration -// does not write, so an accept of it waits for a message nothing in the model -// posts. "" when a send action names it or nothing at all does: a signal nothing -// posts arrives from outside the model, in the tool and here alike. +// of the document sends it or a signal specializing it, only a button of the +// prototype, which the migration does not write, so an accept of it waits for a +// message nothing in the model posts. A signal nothing posts arrives from outside +// the model, in the tool and here alike, and gets no note. func (m *migration) unsentNote(sig *sysmlv1.Element) string { - if sig == nil || m.senders.buttons[sig] == 0 || m.sentByAction(sig) { - return "" + n, special := m.buttonsPosting(sig) + what := describe(sig) + if len(special) > 0 { + names := make([]string, len(special)) + for i, s := range special { + names[i] = describe(s) + } + what += " or a signal specializing it (" + strings.Join(names, ", ") + ")" } - return "no send action of the document sends " + describe(sig) + ", which only " + count(m.senders.buttons[sig], "button") + + return "no send action of the document sends " + what + ", which only " + count(n, "button") + " of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts" } -// sentByAction reports whether a send action of the model sends sig or a +// buttonsPosting counts the buttons of the tool's UI prototype posting sig or a +// signal specializing it, which an accept of sig takes too, and lists those +// specializing signals by id. +func (m *migration) buttonsPosting(sig *sysmlv1.Element) (n int, special []*sysmlv1.Element) { + for posted, buttons := range m.senders.buttons { + switch { + case posted == sig: + n += buttons + case m.inherits(posted, sig): + n += buttons + special = append(special, posted) + } + } + sort.Slice(special, func(i, j int) bool { return special[i].ID < special[j].ID }) + return n, special +} + +// sentByAction reports whether a send action the migration wrote sends sig or a // signal specializing it, either of which an accept of sig takes. func (m *migration) sentByAction(sig *sysmlv1.Element) bool { - if m.senders.actions[sig] { + if m.senders.sent[sig] { return true } - for sent := range m.senders.actions { + for sent := range m.senders.sent { if m.inherits(sent, sig) { return true } @@ -94,8 +149,9 @@ func uiFrameName(e *sysmlv1.Element) string { // clockWait names the first wait on the clock the behavior b, or a behavior an // action of it calls, is written with: a duration constraint bounding it or one -// of its nodes, written as a wait before it, or an accept of a time event. "" -// when none. +// of its nodes, written as a wait before it, or an accept of a time event that is +// written. "" when none: a bound or a trigger the writer leaves as a placeholder +// is no wait. func (m *migration) clockWait(b *sysmlv1.Element) string { return m.clockWaitIn(b, map[*sysmlv1.Element]bool{}) } @@ -105,8 +161,8 @@ func (m *migration) clockWaitIn(b *sysmlv1.Element, seen map[*sysmlv1.Element]bo return "" } seen[b] = true - if dc := m.waitBound(b); dc != nil { - return "the duration constraint " + describe(dc) + " on " + describe(b) + if w := m.waitOf(b); w.ok { + return "the duration constraint " + describe(w.dc) + " on " + describe(b) } found := "" m.walkActions(b, func(n *sysmlv1.Element) { @@ -115,35 +171,47 @@ func (m *migration) clockWaitIn(b *sysmlv1.Element, seen map[*sysmlv1.Element]bo } switch n.Type { case "AcceptEventAction": - for _, t := range n.Owned("trigger") { - if ev := m.model.Ref(t, "event"); ev != nil && ev.Type == "TimeEvent" { - found = "the accept of a time event " + describe(n) + " in " + describe(b) - return - } + if m.acceptsTime(n) { + found = "the accept of a time event " + describe(n) + " in " + describe(b) + return } case "CallBehaviorAction": - found = m.clockWaitIn(m.model.Ref(n, "behavior"), seen) + found = m.calledWait(m.model.Ref(n, "behavior"), seen) case "CallOperationAction": if op := m.model.Ref(n, "operation"); op != nil { - found = m.clockWaitIn(m.bodyMethod(op), seen) + found = m.calledWait(m.bodyMethod(op), seen) } } - if dc := m.waitBound(n); found == "" && dc != nil { - found = "the duration constraint " + describe(dc) + " on " + describe(n) + " in " + describe(b) + if w := m.waitOf(n); found == "" && w.ok { + found = "the duration constraint " + describe(w.dc) + " on " + describe(n) + " in " + describe(b) } }) return found } -// waitBound is the duration constraint on e that is written as a wait before -// it — the first with an interval, as waitFor writes it; nil when none is. -func (m *migration) waitBound(e *sysmlv1.Element) *sysmlv1.Element { - for _, dc := range m.bounded[e] { - if spec := firstOwned(dc, "specification"); spec != nil && (spec.Type == "DurationInterval" || spec.Type == "Interval") { - return dc - } +// calledWait is the clock wait of a called behavior, which a call performs only +// when the behavior has a v2 declaration; a call of one without is a placeholder. +func (m *migration) calledWait(b *sysmlv1.Element, seen map[*sysmlv1.Element]bool) string { + if !m.written(b) { + return "" + } + return m.clockWaitIn(b, seen) +} + +// acceptsTime reports whether the accept action n is written accepting a time +// event: its first trigger, the one written, names a time event whose time the +// writer spells. +func (m *migration) acceptsTime(n *sysmlv1.Element) bool { + triggers := n.Owned("trigger") + if len(triggers) == 0 { + return false + } + ev := m.model.Ref(triggers[0], "event") + if ev == nil || ev.Type != "TimeEvent" { + return false } - return nil + _, _, ok := m.acceptClause(ev, n.Parent, "") + return ok } // instantWaitNote says that a behavior performed at an instant — a state's entry diff --git a/internal/translate/migrate/states.go b/internal/translate/migrate/states.go index 855fc1899f..e037f5e279 100644 --- a/internal/translate/migrate/states.go +++ b/internal/translate/migrate/states.go @@ -2704,7 +2704,7 @@ func (s *stateRegion) transitionAccepts(t *sysmlv1.Element, triggers []*sysmlv1. } note = joinNotes(note, rnote) if ev := s.m.model.Ref(tr, "event"); ev != nil && ev.Type == "SignalEvent" { - note = joinNotes(note, s.m.unsentNote(s.m.model.Ref(ev, "signal"))) + s.m.uiAccept(s.m.model.Ref(ev, "signal"), tr) } s.m.add(tr, verdictFor(note), "", joinNotes(note, rinfo)) accepts = append(accepts, routes...) diff --git a/tests/migrate/instant_waits_test.go b/tests/migrate/instant_waits_test.go index 37e6dcdbcf..c951a9c839 100644 --- a/tests/migrate/instant_waits_test.go +++ b/tests/migrate/instant_waits_test.go @@ -12,7 +12,10 @@ import ( // clock — by a duration constraint on itself or one of its nodes, a call of an // activity or an operation whose method has one, or an accept of a time event — // is ledgered as an approximation naming the wait: a run stops there. A do -// activity may wait, and gets no such note. +// activity may wait, and gets no such note. Nor does a behavior whose only +// waits the writer leaves as placeholders — a time event whose time it cannot +// spell, a duration constraint whose interval no one wait stands for — since +// nothing written waits. func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { r := migrateXMI(t, "instant_waits") wantClean(t, "t.sysml", r) @@ -26,4 +29,11 @@ func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { t.Errorf("the do action Blink is noted as waiting for the clock: %s", e.Note) } } + wantNote(t, r, "_sahWait", migrate.Approximated, "the time event's time is not written") + wantNote(t, r, "_dtHold", migrate.Unmapped, "the interval has no max, so the interval is open above") + for _, e := range entriesFor(r, "_sendAckHold") { + if strings.Contains(e.Note, "waits for the clock") { + t.Errorf("the transition effect Send Ack Hold, whose waits are placeholders, is noted as waiting for the clock: %s", e.Note) + } + } } diff --git a/tests/migrate/signal_absent_argument_test.go b/tests/migrate/signal_absent_argument_test.go index c85e35bd4a..b01cc5ff7f 100644 --- a/tests/migrate/signal_absent_argument_test.go +++ b/tests/migrate/signal_absent_argument_test.go @@ -73,3 +73,45 @@ func TestSendOfAbsentArgumentToRequiredAttributeIsLedgered(t *testing.T) { wantNote(t, r, "_send", migrate.Approximated, "the argument pin target admits no value, which the signal's target, declared holding one, cannot: a run reaching the send with none stops at it") wantLine(t, r.Notation, "send new Point(target, note) via tx;") } + +// misroutedSend is a beacon whose activity Hail counts its hails, so it acts on +// the beacon, and sends a Ping, which declares no attribute, passing an argument +// pin anyway, on a port of the unrelated block Relay. +const misroutedSend = ` + + + + + + ` + integerHref + ` + + + + + + JavaScript + hails = hails + 1; + + + + + + + + + + + + ` + +const misroutedSendApplications = ` + + ` + +// A send on a port that is no port of the sender's object keeps the note on its +// arguments beside the one on its port: both say what the send as written drops. +func TestSendOnAnotherObjectsPortKeepsItsArgumentNote(t *testing.T) { + r := migrateDocument(t, misroutedSend, misroutedSendApplications) + wantNote(t, r, "_hailSend", migrate.Approximated, "the signal has no attribute for the argument pin strength, which is not sent; the port Relay::rx is no port of the object the sender acts on; the signal is sent to the sender") + wantLine(t, r.Notation, "send new Ping();") +} diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt index 7134891e25..a5a08c37ef 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt +++ b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt @@ -1,22 +1,29 @@ # SysML v1 to v2 migration report: instant_waits.xmi -# migrated 57 element(s): 48 mapped, 9 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) +# migrated 73 element(s): 61 mapped, 10 approximated, 2 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) -## approximated (9) +## unmapped (2) +TimeEvent _someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) +DurationConstraint Stimulus::Hold::dtHold _dtHold (the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it) + +## approximated (10) Activity Stimulus::Blink _blink -> Stimulus::Blink (also run as the do action of 'On') Activity Stimulus::Cool Down _cool -> Stimulus::'Cool Down' (also run as the exit action of 'On') DurationConstraint Stimulus::Cool Down::dtCool _dtCool -> Stimulus::'Cool Down' (written as a wait drawn uniformly over [1.0, 3.0] s before 'Cool Down'; a tool's fixed min or max mode is a run setting, not the model's) DurationConstraint Stimulus::Send Ack On::dtOn _dtOn -> Stimulus::'Send Ack On' (written as a wait drawn uniformly over [1.0, 3.0] s before 'Turn On'; a tool's fixed min or max mode is a run setting, not the model's) StateMachine Stimulus::Stimulus Behavior _sm -> Stimulus::'Stimulus Behavior' (acts on its owner Stimulus, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Stimulus as its usage stimulus Behavior) Activity Stimulus::Stimulus Behavior::::::Send Ack _sendAck -> Stimulus::'Stimulus Behavior'::unnamed::'Send Ack' (written as the do action of (_tOn); it waits for the clock (the duration constraint 'dtOn' on 'Turn On' in 'Send Ack On'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) -Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed2::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) +AcceptEventAction Stimulus::Stimulus Behavior::::::Send Ack Hold::someday _sahWait -> someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) +Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed3::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) State Stimulus::Stimulus Behavior::::On _on -> On (its entry action Stimulus::Warm Up: it waits for the clock (the accept of a time event 'warmed' in 'Warm Up'), which a v2 entry action, performed whole at the instant it is triggered, may not: a run stops at the wait; its exit action Stimulus::Cool Down: it waits for the clock (the duration constraint 'dtCool' on 'Cool Down'), which a v2 exit action, performed whole at the instant it is triggered, may not: a run stops at the wait) Activity Stimulus::Warm Up _warm -> Stimulus::'Warm Up' (also run as the entry action of 'On') -## mapped (48) +## mapped (61) SignalEvent _cmdOnEv (written where a trigger refers to it, as accept 'Cmd On') +SignalEvent _cmdHoldEv (written where a trigger refers to it, as accept 'Cmd Hold') SignalEvent _cmdOffEv (written where a trigger refers to it, as accept 'Cmd Off') TimeEvent _warmed (written where a trigger refers to it, as accept after 2.0 [SI::s]) TimeEvent _blinked (written where a trigger refers to it, as accept after 1.0 [SI::s]) +Signal Cmd Hold _cmdHold -> 'Cmd Hold' Signal Cmd Off _cmdOff -> 'Cmd Off' Signal Cmd On _cmdOn -> 'Cmd On' Model Model _m (the root model's members are written at the top level) @@ -35,6 +42,9 @@ ControlFlow Stimulus::Delaying:: _delayE2 InitialNode Stimulus::Delaying:: _delayInit -> start AcceptEventAction Stimulus::Delaying::delayed _delayWait -> delayed Trigger Stimulus::Delaying::delayed:: _delayTr +Activity Stimulus::Hold _hold -> Stimulus::Hold +ActivityFinalNode Stimulus::Hold:: _holdFinal -> final +InitialNode Stimulus::Hold:: _holdInit -> start Activity Stimulus::Send Ack On _ackOn -> Stimulus::'Send Ack On' ActivityFinalNode Stimulus::Send Ack On:: _ackFinal -> final ControlFlow Stimulus::Send Ack On:: _ackE2 @@ -44,9 +54,17 @@ Region Stimulus::Stimulus Behavior:: _r (the one region is written as th Pseudostate Stimulus::Stimulus Behavior:::: _init (written as the entry of the region) Transition Stimulus::Stimulus Behavior:::: _tStart Transition Stimulus::Stimulus Behavior:::: _tOn +Transition Stimulus::Stimulus Behavior:::: _tHold Transition Stimulus::Stimulus Behavior:::: _tOff Trigger Stimulus::Stimulus Behavior:::::: _trOn +Trigger Stimulus::Stimulus Behavior:::::: _trHold Trigger Stimulus::Stimulus Behavior:::::: _trOff +Activity Stimulus::Stimulus Behavior::::::Send Ack Hold _sendAckHold -> Stimulus::'Stimulus Behavior'::unnamed2::'Send Ack Hold' (written as the do action of (_tHold)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahFinal -> final +CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahCall -> call +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahE2 +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahE3 +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahInit -> start ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoFinal -> final CallOperationAction Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoCall -> call ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoE2 diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.sysml b/tests/migrate/testdata/xmi/instant_waits.golden.sysml index 012c9d28d6..1fd05e2541 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.sysml +++ b/tests/migrate/testdata/xmi/instant_waits.golden.sysml @@ -1,6 +1,13 @@ item def 'Cmd On'; item def 'Cmd Off'; +item def 'Cmd Hold'; part def Stimulus { + action def Hold { + /* duration constraint on 'Hold' not migrated — the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it */ + first start then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about final; + } action def 'Send Ack On' { first start then wait; action wait accept after RandomFunctions::uniform(1.0, 3.0) [SI::s]; @@ -59,6 +66,19 @@ part def Stimulus { metadata MigrationMetadata::SynthesizedName about call, final; } then On; + transition first Standby accept 'Cmd Hold' + do action 'Send Ack Hold' { + first start then call; + action call : Hold; + first call then someday; + action someday { + /* not migrated: AcceptEventAction 'someday' — the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called */ + } + first someday then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; transition first On accept 'Cmd Off' do action 'Send Ack Off' { first start then call; diff --git a/tests/migrate/testdata/xmi/instant_waits.xmi b/tests/migrate/testdata/xmi/instant_waits.xmi index 2a867bf02a..7d3844caa1 100644 --- a/tests/migrate/testdata/xmi/instant_waits.xmi +++ b/tests/migrate/testdata/xmi/instant_waits.xmi @@ -23,7 +23,23 @@ + + + + + + + + + + + + + + + + @@ -93,6 +109,20 @@ + + + + + + + + + + + + + + diff --git a/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt b/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt index 3cdbf9e109..d8e545068c 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt +++ b/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt @@ -1,18 +1,23 @@ # SysML v1 to v2 migration report: ui_stimuli.xmi -# migrated 41 element(s): 35 mapped, 6 approximated, 0 unmapped (3 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 53 element(s): 44 mapped, 9 approximated, 0 unmapped (5 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (6) +## approximated (9) AcceptEventAction Await Start::started _awAccept -> started (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) Trigger Await Start::started:: _awTr (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) StateMachine Controller::Controller Behavior _sm -> Controller::'Controller Behavior' (the classifier behavior is run by every object of Controller as its usage controller Behavior) Trigger Controller::Controller Behavior:::::: _trStart (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) +Trigger Controller::Controller Behavior:::::: _trPause (no send action of the document sends 'Pause' or a signal specializing it ('Long Pause'), which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) +Trigger Controller::Controller Behavior:::::: _trResume (no send action of the document sends 'Resume', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) Activity Controller::Run _run -> Controller::Run (also run as the do action of 'Running') +SendSignalAction Controller::Run::send resume _sendResume -> 'send resume' (the send passes no argument for the attribute delay of Resume, which must hold a value; v1 sends the signal without it, which v2 does not admit, so the action carries the token and performs nothing) «SimulationConfig» Class Interactive _cfg -> Interactive («SimulationConfig» UI = 'Operate the controller' is the tool's UI prototype, through which a user of the tool's run posts signals and reads values; it has no v2 form, so a run here takes no input from it; the classifier behavior of Controller is a state machine, which a run performs as no action; the configuration only holds 'Controller') -## mapped (35) +## mapped (44) SignalEvent _startEv (written where a trigger refers to it, as accept Start) SignalEvent _stopEv (written where a trigger refers to it, as accept Stop) SignalEvent _resetEv (written where a trigger refers to it, as accept Reset) +SignalEvent _pauseEv (written where a trigger refers to it, as accept Pause) +SignalEvent _resumeEv (written where a trigger refers to it, as accept Resume) SignalEvent _finishEv (written where a trigger refers to it, as accept Finish) Activity Await Start _await -> 'Await Start' ActivityFinalNode Await Start:: _awFinal -> final @@ -25,6 +30,8 @@ Transition Controller::Controller Behavior:::: _t0 Transition Controller::Controller Behavior:::: _tStart Transition Controller::Controller Behavior:::: _tStop Transition Controller::Controller Behavior:::: _tReset +Transition Controller::Controller Behavior:::: _tPause +Transition Controller::Controller Behavior:::: _tResume Transition Controller::Controller Behavior:::: _tFinish Trigger Controller::Controller Behavior:::::: _trStop Trigger Controller::Controller Behavior:::::: _trReset @@ -34,6 +41,7 @@ State Controller::Controller Behavior::::Idle _idle -> Idle State Controller::Controller Behavior::::Running _running -> Running ActivityFinalNode Controller::Run:: _runFinal -> final ControlFlow Controller::Run:: _runE3 +ControlFlow Controller::Run:: _runE4 ControlFlow Controller::Run:: _runE2 InitialNode Controller::Run:: _runInit -> start SendSignalAction Controller::Run::send hard reset _sendHardReset -> 'send hard reset' @@ -41,12 +49,18 @@ SendSignalAction Controller::Run::send stop _sendStop -> 'send stop' Signal Finish _finish -> Finish Package Gui _gui -> Gui Signal Hard Reset _hardReset -> 'Hard Reset' +Signal Long Pause _longPause -> 'Long Pause' Model Model _m (the root model's members are written at the top level) +Signal Pause _pause -> Pause Signal Reset _reset -> Reset +Signal Resume _resume -> Resume +Property Resume::delay _resumeDelay -> Resume::delay Signal Start _start -> Start Signal Stop _stop -> Stop -## skipped (3) +## skipped (5) «Button» Class Gui:: _button («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) «Button» Class Gui:: _resetButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) +«Button» Class Gui:: _pauseButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) +«Button» Class Gui:: _resumeButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) «Frame» Component Gui::Console _frame («Frame» marks the modeling tool's UI prototyping mockup, which has no model meaning) diff --git a/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml b/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml index d7afa9fe79..5575b2f592 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml +++ b/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml @@ -3,6 +3,11 @@ item def Stop; item def Finish; item def Reset; item def 'Hard Reset' :> Reset; +item def Pause; +item def 'Long Pause' :> Pause; +item def Resume { + attribute delay : ScalarValues::Real; +} part def Controller { action def Run { first start then 'send stop'; @@ -13,7 +18,11 @@ part def Controller { action 'send hard reset' { send new 'Hard Reset'(); } - first 'send hard reset' then final; + first 'send hard reset' then 'send resume'; + action 'send resume' { + /* not migrated: SendSignalAction 'send resume' — the send passes no argument for the attribute delay of Resume, which must hold a value; v1 sends the signal without it, which v2 does not admit, so the action carries the token and performs nothing */ + } + first 'send resume' then final; action final terminate; metadata MigrationMetadata::SynthesizedName about final; } @@ -26,6 +35,8 @@ part def Controller { transition first Idle accept Start then Running; transition first Running accept Stop then Idle; transition first Running accept Reset then Idle; + transition first Running accept Pause then Idle; + transition first Idle accept Resume then Running; transition first Idle accept Finish then done; } exhibit state 'controller Behavior' : 'Controller Behavior'; diff --git a/tests/migrate/testdata/xmi/ui_stimuli.xmi b/tests/migrate/testdata/xmi/ui_stimuli.xmi index f65bb78858..2b87ef3f9c 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.xmi +++ b/tests/migrate/testdata/xmi/ui_stimuli.xmi @@ -12,7 +12,19 @@ + + + + + + + + + + + + @@ -22,10 +34,12 @@ + - + + @@ -43,6 +57,12 @@ + + + + + + @@ -59,11 +79,13 @@ - + + + @@ -73,5 +95,9 @@ + + + + diff --git a/tests/migrate/ui_stimuli_test.go b/tests/migrate/ui_stimuli_test.go index 4192eb8a9b..bf45826cab 100644 --- a/tests/migrate/ui_stimuli_test.go +++ b/tests/migrate/ui_stimuli_test.go @@ -12,7 +12,9 @@ import ( // a configuration whose UI is that prototype says a run here takes no input // from it. A signal a send action sends — itself or a signal specializing it, // which the accept takes too — or one nothing at all posts, is an ordinary -// accept. +// accept. A button posting a signal specializing the accepted one counts as +// posting it, and a send action written as a placeholder, which performs +// nothing, is no sender. func TestSignalsOnlyTheToolsUIPostsAreLedgered(t *testing.T) { r := migrateXMI(t, "ui_stimuli") wantClean(t, "t.sysml", r) @@ -22,6 +24,9 @@ func TestSignalsOnlyTheToolsUIPostsAreLedgered(t *testing.T) { wantNote(t, r, "_trStop", migrate.Mapped, "") wantNote(t, r, "_trReset", migrate.Mapped, "") wantNote(t, r, "_trFinish", migrate.Mapped, "") + wantNote(t, r, "_trPause", migrate.Approximated, "no send action of the document sends 'Pause' or a signal specializing it ('Long Pause'), which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts") + wantNote(t, r, "_sendResume", migrate.Approximated, "the send passes no argument for the attribute delay of Resume, which must hold a value") + wantNote(t, r, "_trResume", migrate.Approximated, "no send action of the document sends 'Resume', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts") wantNote(t, r, "_cfg", migrate.Approximated, "«SimulationConfig» UI = 'Operate the controller' is the tool's UI prototype, through which a user of the tool's run posts signals and reads values; it has no v2 form, so a run here takes no input from it") wantLine(t, r.Notation, "UI = Gui::Console") } From 362fb4ab1f4861748218fe2151403f40e5d3e619 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 23:33:25 +0000 Subject: [PATCH 3/4] fix(migrate): follow a call to its behavior's clock wait only when the call is written invoking it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A clock wait in a behavior named by a call written as a placeholder — one the writer refuses, its context unbound or its callee undeclared — or by a call that never fires, its target pin fed by nothing, is no wait of the caller: the writer invokes nothing there. The clock-wait scan now links the caller's graph as the writer does and follows only the calls written as invocations, skipping a node whose pin nothing fills, as the writer writes it without its wait. Co-Authored-By: jason.han --- .../migrate-sender-and-wait-notes.fixed.md | 2 +- docs/reference/sysml-v1-migration.md | 2 +- internal/translate/migrate/senders.go | 105 +++++++++++++----- tests/migrate/instant_waits_test.go | 13 ++- .../xmi/instant_waits.golden.report.txt | 42 ++++++- .../testdata/xmi/instant_waits.golden.sysml | 36 ++++++ tests/migrate/testdata/xmi/instant_waits.xmi | 50 +++++++++ 7 files changed, 214 insertions(+), 36 deletions(-) diff --git a/changes/unreleased/migrate-sender-and-wait-notes.fixed.md b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md index 3dfd715ff0..5b3cad33ed 100644 --- a/changes/unreleased/migrate-sender-and-wait-notes.fixed.md +++ b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md @@ -1 +1 @@ -- **The migrator's ledger counts a send action as a sender only once it is written, and a button of the tool's UI prototype as posting the signals it specializes.** An accept of a signal is noted as waiting for a message only the tool's UI posts when no written send sends it or a signal specializing it — a send left as a placeholder, which performs nothing, no longer hides the note — and a button posting a specialization of the accepted signal now raises it. A send whose port is not the sender's keeps the note on its arguments beside the one on its port, and an entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes, not for a time event or duration constraint it leaves as a placeholder. +- **The migrator's ledger counts a send action as a sender only once it is written, and a button of the tool's UI prototype as posting the signals it specializes.** An accept of a signal is noted as waiting for a message only the tool's UI posts when no written send sends it or a signal specializing it — a send left as a placeholder, which performs nothing, no longer hides the note — and a button posting a specialization of the accepted signal now raises it. A send whose port is not the sender's keeps the note on its arguments beside the one on its port, and an entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes, not for a time event or duration constraint it leaves as a placeholder, nor for one in a behavior named by a call written as a placeholder or never firing, which invokes nothing. diff --git a/docs/reference/sysml-v1-migration.md b/docs/reference/sysml-v1-migration.md index 7f2d167f45..dc6509d1d5 100644 --- a/docs/reference/sysml-v1-migration.md +++ b/docs/reference/sysml-v1-migration.md @@ -255,7 +255,7 @@ returned over the service yet. | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity with no nodes | an empty action: `entry action x;` in a state, `do action x { }` on a transition, whose target follows on the next line | mapped (the note says the action is empty) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity whose every action node is refused | the action, holding the flow and a comment for each refused node; the behavior runs nothing | approximated (each node: **unmapped**) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an OpaqueBehavior in a language the mapping cannot write | the action, holding the body as a comment | approximated | -| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes that is written as a wait, an accept of a time event whose time is written, or a call of an activity, or of an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted; a constraint or time event left as a placeholder is no wait, and is not noted either) | approximated | +| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes that is written as a wait, an accept of a time event whose time is written, or a call it writes invoking an activity, or an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted; a constraint or time event left as a placeholder is no wait, and is not noted either, nor is one in a behavior named by a call written as a placeholder or never firing, which invokes nothing) | approximated | | Transition `effect` referring to a behavior owned elsewhere | `do action : Def` on the transition, the target following on the next line; the behavior's own `action def` is written once where it is owned | mapped | | `entry`, `doActivity`, `exit` behavior or transition `effect` referring to a behavior that is not written, or is written as something no state runs (a StateMachine, for one) | comment in the state's body or before the transition (a `/* */` comment is admitted only where a member may appear, not between the transition's clauses); the state or transition is written without it | approximated (the state or transition: "its … is not run"; a behavior not written: **unmapped**) | | Transition `effect` with `in` parameters | the accepted signal is named, `accept sig : Sig`, and each parameter typed by the signal (or a general of it), or the sole untyped one, is bound to it: `in p : Sig = sig;`; a parameter of another type takes no value | mapped (an unbound parameter: approximated) | diff --git a/internal/translate/migrate/senders.go b/internal/translate/migrate/senders.go index a9fa8e1646..baba162833 100644 --- a/internal/translate/migrate/senders.go +++ b/internal/translate/migrate/senders.go @@ -147,11 +147,12 @@ func uiFrameName(e *sysmlv1.Element) string { return describe(e) } -// clockWait names the first wait on the clock the behavior b, or a behavior an -// action of it calls, is written with: a duration constraint bounding it or one +// clockWait names the first wait on the clock the behavior b, or a behavior a +// call of it invokes, is written with: a duration constraint bounding it or one // of its nodes, written as a wait before it, or an accept of a time event that is // written. "" when none: a bound or a trigger the writer leaves as a placeholder -// is no wait. +// is no wait, nor is one in a behavior named by a call written as a placeholder +// or never firing, which invokes nothing. func (m *migration) clockWait(b *sysmlv1.Element) string { return m.clockWaitIn(b, map[*sysmlv1.Element]bool{}) } @@ -164,38 +165,86 @@ func (m *migration) clockWaitIn(b *sysmlv1.Element, seen map[*sysmlv1.Element]bo if w := m.waitOf(b); w.ok { return "the duration constraint " + describe(w.dc) + " on " + describe(b) } - found := "" - m.walkActions(b, func(n *sysmlv1.Element) { + def := b + if op := m.methodOf[b]; op != nil { + def = op + } + return m.graphWait(m.scanGraph(b, def), b, seen) +} + +// scanGraph links an activity graph as for writing, without writing, far enough +// to tell the nodes written as placeholders and the pins nothing fills. +func (m *migration) scanGraph(act, def *sysmlv1.Element) *activity { + a := m.newActivity(act, def) + a.link() + a.resolveData() + a.deaden() + return a +} + +// graphWait names the first wait the graph a of the behavior b is written with, +// in the order the writer meets its nodes: the wait before a node a duration +// constrains, an accept of a time event, the wait of the behavior a call +// invokes, or one in a structured node's own graph. A node that never fires, +// whose pin nothing fills, is written without its wait. +func (m *migration) graphWait(a *activity, b *sysmlv1.Element, seen map[*sysmlv1.Element]bool) string { + for _, n := range a.nodes { + if a.starvedPin(n) != nil { + continue + } + if w := m.waitOf(n); w.ok { + return "the duration constraint " + describe(w.dc) + " on " + describe(n) + " in " + describe(b) + } + found := "" + switch { + case n.Type == "AcceptEventAction" && m.acceptsTime(n): + found = "the accept of a time event " + describe(n) + " in " + describe(b) + case isStructured(n): + found = m.graphWait(m.scanGraph(n, n), b, seen) + case a.invokes(n): + found = m.clockWaitIn(a.callee(n), seen) + } if found != "" { - return + return found } - switch n.Type { - case "AcceptEventAction": - if m.acceptsTime(n) { - found = "the accept of a time event " + describe(n) + " in " + describe(b) - return - } - case "CallBehaviorAction": - found = m.calledWait(m.model.Ref(n, "behavior"), seen) - case "CallOperationAction": - if op := m.model.Ref(n, "operation"); op != nil { - found = m.calledWait(m.bodyMethod(op), seen) - } + } + return "" +} + +// invokes reports whether the call n is written invoking the behavior it names +// or the operation's method: not when it is a placeholder, calls a function of +// the library, or is the empty step standing for a call of an operation written +// as a usage whose target pin names no object read from this. +func (a *activity) invokes(n *sysmlv1.Element) bool { + if a.dead[n] { + return false + } + switch n.Type { + case "CallBehaviorAction": + return a.m.primitiveCalled(n) == nil + case "CallOperationAction": + op := a.m.model.Ref(n, "operation") + t := firstOwned(n, "target") + if !a.m.asUsage[op] || t == nil { + return true } - if w := m.waitOf(n); found == "" && w.ok { - found = "the duration constraint " + describe(w.dc) + " on " + describe(n) + " in " + describe(b) + if port := a.m.model.Ref(n, "onPort"); port != nil { + _, _, ok := a.portReceiver(port, t, op) + return ok } - }) - return found + _, _, ok := a.receiverOf(t, op) + return ok + } + return false } -// calledWait is the clock wait of a called behavior, which a call performs only -// when the behavior has a v2 declaration; a call of one without is a placeholder. -func (m *migration) calledWait(b *sysmlv1.Element, seen map[*sysmlv1.Element]bool) string { - if !m.written(b) { - return "" +// callee is the activity the call n invokes: the behavior it names, or the +// method of the operation. +func (a *activity) callee(n *sysmlv1.Element) *sysmlv1.Element { + if n.Type == "CallOperationAction" { + return a.m.bodyMethod(a.m.model.Ref(n, "operation")) } - return m.clockWaitIn(b, seen) + return a.m.model.Ref(n, "behavior") } // acceptsTime reports whether the accept action n is written accepting a time diff --git a/tests/migrate/instant_waits_test.go b/tests/migrate/instant_waits_test.go index c951a9c839..49f54905dd 100644 --- a/tests/migrate/instant_waits_test.go +++ b/tests/migrate/instant_waits_test.go @@ -15,7 +15,8 @@ import ( // activity may wait, and gets no such note. Nor does a behavior whose only // waits the writer leaves as placeholders — a time event whose time it cannot // spell, a duration constraint whose interval no one wait stands for — since -// nothing written waits. +// nothing written waits; nor one whose waits lie in a behavior named by a call +// written as a placeholder or never firing, which invokes nothing. func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { r := migrateXMI(t, "instant_waits") wantClean(t, "t.sysml", r) @@ -36,4 +37,14 @@ func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { t.Errorf("the transition effect Send Ack Hold, whose waits are placeholders, is noted as waiting for the clock: %s", e.Note) } } + wantNote(t, r, "_sasCall", migrate.Approximated, "the behavior acts on a Relay through its parameter context, which is left unbound") + wantNote(t, r, "_sapCall", migrate.Approximated, "the action never fires: its input pin 'target' must hold a value, but no object flow feeds it") + for _, id := range []string{"_sendAckSettle", "_sendAckPause"} { + wantNote(t, r, id, migrate.Mapped, "written as the do action of") + for _, e := range entriesFor(r, id) { + if strings.Contains(e.Note, "waits for the clock") { + t.Errorf("the transition effect %s, whose call of a waiting behavior invokes nothing, is noted as waiting for the clock: %s", id, e.Note) + } + } + } } diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt index a5a08c37ef..0f03606c4b 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt +++ b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt @@ -1,11 +1,13 @@ # SysML v1 to v2 migration report: instant_waits.xmi -# migrated 73 element(s): 61 mapped, 10 approximated, 2 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) +# migrated 105 element(s): 88 mapped, 13 approximated, 4 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) -## unmapped (2) +## unmapped (4) TimeEvent _someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) DurationConstraint Stimulus::Hold::dtHold _dtHold (the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it) +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapE1 (the edge leads to (_sapCall), which never fires: no value reaches its input pin 'target') +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapE2 (the edge leaves (_sapCall), which never fires, so no token travels it) -## approximated (10) +## approximated (13) Activity Stimulus::Blink _blink -> Stimulus::Blink (also run as the do action of 'On') Activity Stimulus::Cool Down _cool -> Stimulus::'Cool Down' (also run as the exit action of 'On') DurationConstraint Stimulus::Cool Down::dtCool _dtCool -> Stimulus::'Cool Down' (written as a wait drawn uniformly over [1.0, 3.0] s before 'Cool Down'; a tool's fixed min or max mode is a run setting, not the model's) @@ -13,20 +15,39 @@ DurationConstraint Stimulus::Send Ack On::dtOn _dtOn -> Stimulus::'Send Ack On' StateMachine Stimulus::Stimulus Behavior _sm -> Stimulus::'Stimulus Behavior' (acts on its owner Stimulus, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Stimulus as its usage stimulus Behavior) Activity Stimulus::Stimulus Behavior::::::Send Ack _sendAck -> Stimulus::'Stimulus Behavior'::unnamed::'Send Ack' (written as the do action of (_tOn); it waits for the clock (the duration constraint 'dtOn' on 'Turn On' in 'Send Ack On'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) AcceptEventAction Stimulus::Stimulus Behavior::::::Send Ack Hold::someday _sahWait -> someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) -Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed3::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) +Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed5::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) +CallOperationAction Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapCall -> call (the call runs in the caller's context: no flow feeds its target pin; the action never fires: its input pin 'target' must hold a value, but no object flow feeds it and it holds no value; no succession leads to it or leaves it, where v1 would wait on it forever) +InputPin Stimulus::Stimulus Behavior::::::Send Ack Pause::::target _sapTarget (the target pin is not written; the call runs in the caller's context) +CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasCall -> call (the behavior acts on a Relay through its parameter context, which is left unbound: the caller is a Stimulus, which is no Relay and has no part that is one; v1 runs Relay::Settle on the caller's object, which lacks the ports it goes through, so the action carries the token and performs nothing) State Stimulus::Stimulus Behavior::::On _on -> On (its entry action Stimulus::Warm Up: it waits for the clock (the accept of a time event 'warmed' in 'Warm Up'), which a v2 entry action, performed whole at the instant it is triggered, may not: a run stops at the wait; its exit action Stimulus::Cool Down: it waits for the clock (the duration constraint 'dtCool' on 'Cool Down'), which a v2 exit action, performed whole at the instant it is triggered, may not: a run stops at the wait) Activity Stimulus::Warm Up _warm -> Stimulus::'Warm Up' (also run as the entry action of 'On') -## mapped (61) +## mapped (88) SignalEvent _cmdOnEv (written where a trigger refers to it, as accept 'Cmd On') SignalEvent _cmdHoldEv (written where a trigger refers to it, as accept 'Cmd Hold') +SignalEvent _cmdSettleEv (written where a trigger refers to it, as accept 'Cmd Settle') +SignalEvent _cmdPauseEv (written where a trigger refers to it, as accept 'Cmd Pause') SignalEvent _cmdOffEv (written where a trigger refers to it, as accept 'Cmd Off') +TimeEvent _settled (written where a trigger refers to it, as accept after 1.0 [SI::s]) TimeEvent _warmed (written where a trigger refers to it, as accept after 2.0 [SI::s]) TimeEvent _blinked (written where a trigger refers to it, as accept after 1.0 [SI::s]) Signal Cmd Hold _cmdHold -> 'Cmd Hold' Signal Cmd Off _cmdOff -> 'Cmd Off' Signal Cmd On _cmdOn -> 'Cmd On' +Signal Cmd Pause _cmdPause -> 'Cmd Pause' +Signal Cmd Settle _cmdSettle -> 'Cmd Settle' Model Model _m (the root model's members are written at the top level) +«Block» Class Relay _relay -> Relay +Activity Relay::Settle _settle -> Relay::Settle (acts on its owner Relay, which it takes as its parameter context, since its this is the def's own occurrence) +ActivityFinalNode Relay::Settle:: _settleFinal -> final +ControlFlow Relay::Settle:: _settleE2 +ControlFlow Relay::Settle:: _settleE3 +InitialNode Relay::Settle:: _settleInit -> start +ReadStructuralFeatureAction Relay::Settle::read gain _readGain -> 'read gain' +OutputPin Relay::Settle::read gain::result _readGainR -> Relay::Settle::'read gain'.result +AcceptEventAction Relay::Settle::settled _settleWait -> settled +Trigger Relay::Settle::settled:: _settleTr +Property Relay::gain _gain -> Relay::gain «Block» Class Stimulus _stim -> Stimulus ActivityFinalNode Stimulus::Blink:: _blinkFinal -> final ControlFlow Stimulus::Blink:: _blinkE2 @@ -55,9 +76,13 @@ Pseudostate Stimulus::Stimulus Behavior:::: _init (written Transition Stimulus::Stimulus Behavior:::: _tStart Transition Stimulus::Stimulus Behavior:::: _tOn Transition Stimulus::Stimulus Behavior:::: _tHold +Transition Stimulus::Stimulus Behavior:::: _tSettle +Transition Stimulus::Stimulus Behavior:::: _tPause Transition Stimulus::Stimulus Behavior:::: _tOff Trigger Stimulus::Stimulus Behavior:::::: _trOn Trigger Stimulus::Stimulus Behavior:::::: _trHold +Trigger Stimulus::Stimulus Behavior:::::: _trSettle +Trigger Stimulus::Stimulus Behavior:::::: _trPause Trigger Stimulus::Stimulus Behavior:::::: _trOff Activity Stimulus::Stimulus Behavior::::::Send Ack Hold _sendAckHold -> Stimulus::'Stimulus Behavior'::unnamed2::'Send Ack Hold' (written as the do action of (_tHold)) ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahFinal -> final @@ -69,6 +94,13 @@ ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack CallOperationAction Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoCall -> call ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoE2 InitialNode Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoInit -> start +Activity Stimulus::Stimulus Behavior::::::Send Ack Pause _sendAckPause -> Stimulus::'Stimulus Behavior'::unnamed4::'Send Ack Pause' (written as the do action of (_tPause)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapFinal -> final +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapInit -> start +Activity Stimulus::Stimulus Behavior::::::Send Ack Settle _sendAckSettle -> Stimulus::'Stimulus Behavior'::unnamed3::'Send Ack Settle' (written as the do action of (_tSettle)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasFinal -> final +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasE2 +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasInit -> start ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack:: _saFinal -> final CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack:: _saCall -> call ControlFlow Stimulus::Stimulus Behavior::::::Send Ack:: _saE2 diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.sysml b/tests/migrate/testdata/xmi/instant_waits.golden.sysml index 1fd05e2541..049056025d 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.sysml +++ b/tests/migrate/testdata/xmi/instant_waits.golden.sysml @@ -1,6 +1,23 @@ item def 'Cmd On'; item def 'Cmd Off'; item def 'Cmd Hold'; +item def 'Cmd Settle'; +item def 'Cmd Pause'; +part def Relay { + attribute gain : ScalarValues::Real default = 1.0; + action def Settle { + in ref context : Relay[1]; + first start then 'read gain'; + action 'read gain' { + out result[1] = context.gain; + } + first 'read gain' then settled; + action settled accept after 1.0 [SI::s]; + first settled then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about final; + } +} part def Stimulus { action def Hold { /* duration constraint on 'Hold' not migrated — the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it */ @@ -79,6 +96,25 @@ part def Stimulus { metadata MigrationMetadata::SynthesizedName about call, final; } then Standby; + transition first Standby accept 'Cmd Settle' + do action 'Send Ack Settle' { + first start then call; + action call { + /* not migrated: CallBehaviorAction (_sasCall) — the behavior acts on a Relay through its parameter context, which is left unbound: the caller is a Stimulus, which is no Relay and has no part that is one; v1 runs Relay::Settle on the caller's object, which lacks the ports it goes through, so the action carries the token and performs nothing */ + } + first call then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; + transition first Standby accept 'Cmd Pause' + do action 'Send Ack Pause' { + /* not migrated: ControlFlow (_sapE1) — the edge leads to (_sapCall), which never fires: no value reaches its input pin 'target' */ + action call : Delay; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; transition first On accept 'Cmd Off' do action 'Send Ack Off' { first start then call; diff --git a/tests/migrate/testdata/xmi/instant_waits.xmi b/tests/migrate/testdata/xmi/instant_waits.xmi index 7d3844caa1..7d2995e849 100644 --- a/tests/migrate/testdata/xmi/instant_waits.xmi +++ b/tests/migrate/testdata/xmi/instant_waits.xmi @@ -29,6 +29,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -123,6 +149,29 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -138,4 +187,5 @@ + From 7b16b9ded5f6cd05037c6ab47bf24c786e1e6046 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 23:35:31 +0000 Subject: [PATCH 4/4] refactor(migrate): shorten the clock-wait scan's doc comments Co-Authored-By: jason.han --- internal/translate/migrate/senders.go | 28 +++++++++------------------ 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/internal/translate/migrate/senders.go b/internal/translate/migrate/senders.go index baba162833..7c89fb37de 100644 --- a/internal/translate/migrate/senders.go +++ b/internal/translate/migrate/senders.go @@ -147,12 +147,8 @@ func uiFrameName(e *sysmlv1.Element) string { return describe(e) } -// clockWait names the first wait on the clock the behavior b, or a behavior a -// call of it invokes, is written with: a duration constraint bounding it or one -// of its nodes, written as a wait before it, or an accept of a time event that is -// written. "" when none: a bound or a trigger the writer leaves as a placeholder -// is no wait, nor is one in a behavior named by a call written as a placeholder -// or never firing, which invokes nothing. +// clockWait names the first wait on the clock written for the behavior b or a +// behavior a call of it invokes; "" when none is written (placeholders are no wait). func (m *migration) clockWait(b *sysmlv1.Element) string { return m.clockWaitIn(b, map[*sysmlv1.Element]bool{}) } @@ -172,8 +168,8 @@ func (m *migration) clockWaitIn(b *sysmlv1.Element, seen map[*sysmlv1.Element]bo return m.graphWait(m.scanGraph(b, def), b, seen) } -// scanGraph links an activity graph as for writing, without writing, far enough -// to tell the nodes written as placeholders and the pins nothing fills. +// scanGraph links an activity graph as the writer does, without writing, far +// enough to tell the nodes written as placeholders and the pins nothing fills. func (m *migration) scanGraph(act, def *sysmlv1.Element) *activity { a := m.newActivity(act, def) a.link() @@ -182,11 +178,8 @@ func (m *migration) scanGraph(act, def *sysmlv1.Element) *activity { return a } -// graphWait names the first wait the graph a of the behavior b is written with, -// in the order the writer meets its nodes: the wait before a node a duration -// constrains, an accept of a time event, the wait of the behavior a call -// invokes, or one in a structured node's own graph. A node that never fires, -// whose pin nothing fills, is written without its wait. +// graphWait names the first wait written for the graph a of the behavior b, in +// writer order; a node whose pin nothing fills is written without its wait. func (m *migration) graphWait(a *activity, b *sysmlv1.Element, seen map[*sysmlv1.Element]bool) string { for _, n := range a.nodes { if a.starvedPin(n) != nil { @@ -211,10 +204,8 @@ func (m *migration) graphWait(a *activity, b *sysmlv1.Element, seen map[*sysmlv1 return "" } -// invokes reports whether the call n is written invoking the behavior it names -// or the operation's method: not when it is a placeholder, calls a function of -// the library, or is the empty step standing for a call of an operation written -// as a usage whose target pin names no object read from this. +// invokes reports whether the call n is written invoking its callee: not as a +// placeholder, a library function, or the empty step of a usage with no receiver. func (a *activity) invokes(n *sysmlv1.Element) bool { if a.dead[n] { return false @@ -238,8 +229,7 @@ func (a *activity) invokes(n *sysmlv1.Element) bool { return false } -// callee is the activity the call n invokes: the behavior it names, or the -// method of the operation. +// callee is the activity the call n invokes: its behavior, or the operation's method. func (a *activity) callee(n *sysmlv1.Element) *sysmlv1.Element { if n.Type == "CallOperationAction" { return a.m.bodyMethod(a.m.model.Ref(n, "operation"))