Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unreleased/binding-flow-connector-objects.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Binding and non-message flow usages now materialize as connector objects.** Their ends hold the connected feature values through the same connector path used by `connect` usages; message flows and one-ended bindings remain excluded.
7 changes: 3 additions & 4 deletions docs/project/spec-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -1921,9 +1921,9 @@ semantics layer over the conjugation parity of the typing/specialization chain.
| The `perform X;` shorthand is an action node named X | `lower/action_graph.go` `getNodeName` | `conformance/action_perform_shorthand.sysml` (`succession first start then increment;` names the perform statement) | ✅ Faithful |
| N-ary connector ends (`connection link connect (a, b, c)`), SysML v2 7.13.2, 8.3.13 | `parser/defusage.go` `parseConnectorEnds` (parenthesized end list, reached by both the named declaration and the anonymous `connect …;` body member) over `parseConnectorEnd`, which takes exactly one target per end and only the relationship forms the `ConnectorEnd` production allows (`::>`, `references`, an explicit `:>>`), so a comma separates ends instead of being read as a further relationship of the first one; `passes/constraint.go` `checkConnectorEnds` (arity by kind, the interface case gated on `semantics.Model.IsBinaryConnector`, since `Interfaces::Interface` is n-ary — `ref port :>> participant : Port[2..*]` — and only `BinaryInterface` narrows it to two); `semantics/implicit.go` gives exactly two-ended untyped interface/connection definitions and usages their binary bases; `lower/connection.go` `lowerConnections`, `PeerPorts` | `parse/connection_nary.golden`, `parser/connector_ends_nary_test.go` `TestParseNaryConnectorEndsKeepsEveryEnd`, `parser/negative_test.go` (`nary_connect_unclosed`, `nary_connect_trailing_comma`, `nary_connect_empty`), `passes/constraint_test.go` `TestConstraintConnectionNaryEndCountReachesTheChecker`, `passes/w9c_rules_test.go` `TestW9CBinaryInterfaceEndDiamondWarns`, `TestW9CNonBinaryConnectorEndsStaySilent`, `TestConstraintBinaryInterfaceNaryFails`, `lower/connection_test.go` `TestLowerNaryConnectionKeepsEveryEnd` and `TestLowerAnonymousNaryConnectionKeepsEveryEnd`, `parser/connector_ends_nary_test.go` `TestParseAnonymousInlineConnectKeepsEveryEnd`, conformance `action_port_communication_nary.sysml` and `action_port_communication_nary_anonymous.sysml` | ✅ Faithful: SysML v2 §7.14.1 permits three or more ends on a general interface; §7.14.2 and §8.3.14.2 constrain `BinaryInterface`. Exactly two-ended untyped interfaces and connections receive binary typing, an explicit binary interface with too many ends errors, and a three-ended general interface remains general. The universal `InterfaceUsage_Invalid.sysml.xt:49` expectation is a pilot limitation |
| Anonymous binary allocation (`allocate torqueGenerator to powerTrain`) | `parser/defusage.go` `atAllocateShorthand` | `parse/perform_reference.golden` | ✅ Faithful (both names are connector ends; formerly the first was read as the usage's name) |
| An object of a connector usage holds the features it connects at its ends (`connection link : Link connect a.p to b.q` makes `link.source` **be** `a.p`), KerML 7.4.6, SysML v2 7.13.2 | `runtime/connector.go` `materializeConnectorFeatureValue`, `materializeConnector`, `attachConnectorEnd`, `bindEndFeatureValue`, `bindParticipants`, reached from `runtime/instance.go` `GetFeatureValue`; end features synthesized by `runtime/shape.go` `connectorEndFeatures`; attachments and effective end names by `semantics/connector.go` `Model.ConnectorEndAttachments`, `Model.IsConnectorUsage`; inherited ends aliased by `runtime/subsetting.go` over `Model.ImplicitEndRedefinitions` | `connector_test.go` (`TestConnectorEndsAreTheConnectedFeatures`, `TestWritingAConnectedPortIsReadThroughTheEnd`, `TestConnectorEndFollowsAFeatureChain`, `TestConnectorEndAttachesToAPart`, `TestNaryConnectorKeepsEveryEnd`, `TestRedefinedEndSharesTheInheritedFeatureValue`, `TestEveryConnectorKindAttachesItsEnds`), `conformance/connector_end_identity.sysml` (identity assertions), `semantics/connector_test.go:TestConnectorEndAttachments`, `robustness_test.go:unattachable_connector_end`, `multiplicity_on_a_connector`, `connector_attached_to_itself`, `mutually_attached_connectors` | ✅ Faithful (an end holds the very object the connector attaches to, so writing the connected port is read through the end and two connectors on different ports are distinguishable; ends are attached in declaration order, including n-ary and nested feature chains and an end attached to a part; an end that names no reachable feature is a typed `ErrConnectorEnd` with a source location rather than a fresh object or `<unknown>`, an end naming the connector it belongs to — directly or through another connector — is `ErrCyclicFeatureValue`, and a connector usage holding more than one connector is reported with where it was written) |
| An untyped or anonymous connector usage materializes on the standard library base of its kind (`interface iface connect a.p to b.q;`, `connect a.p to b.q;`), SysML v2 7.13.2, 8.3.13 | `semantics/implicit.go` `implicitUsageBases` (`Connections::Connection`, `Interfaces::Interface`, `Allocations::Allocation`); `runtime/connector.go` `connectorBaseOf`, `anonymousConnectors`; `symbols/builder.go` `usageSymbolKind` (a KerML `connector` is a connection usage) and `semantics/shape.go` `IsShapeFeature` (an allocation usage is a feature) | `conformance/connector_end_identity.sysml`, `ballandchain_interface_connected.sysml`, `connector_test.go` (`TestUntypedConnectorUsageMaterializes`, `TestAnonymousConnectorJoinsItsEnds`, `TestAnonymousConnectorIsMaterializedOnce`, `TestAnonymousSuccessionIsNoConnector`, `TestEveryConnectorKindAttachesItsEnds`), `parse/connection_implicit_type.golden` | ✅ Faithful (a connection, interface, allocation or connector usage that names no definition is an object of its kind's library base with its ends attached, named form and anonymous form alike, and an anonymous one materializes once per object; a flow or binding states its ends by other syntax and is not a `connect` connector — its ends reach routing through lowering, not through connector-end feature values) |
| A flow usage (`flow f from a.out to b.in`) and a binding usage (`binding b bind a.p = b.p`) are connectors of the kernel layer, but state their ends in their own syntax — `Usage.FlowEnds`, and a binding's two `Usage.ConnectorEnds` — rather than in a `connect` clause | `parser/defusage.go` `parseFlowEnds` and `parseBindingDeclaration`/`parseBindingEnds`; `resolve/document.go` `isImplicitCalcResult`; `lower/connection.go` (flow ends reach routing through lowering); `lower/binding.go` `ToBindings`/`lowerBinding`; `runtime/binding.go` `objectBindings`/`resolveBindingValue`/`resolveBindingSet`/`attemptBinding`/`unmaterializedObjectEnd`/`resolveBindingLocation`; `runtime/instance.go` `materializeFeatureValue`; `runtime/invoke_calc.go` `resultBindingExpr`; `semantics/connector.go` `Model.IsConnectorUsage` deliberately covers only the `connect` forms | `parse/connection_implicit_type.golden` (`flow f from a.p to b.p;`, `binding bnd bind a.p = b.p;`), `lower/connection_test.go`, `lower/binding_test.go` (`TestToBindingsKeepsMultipleContributors`), `runtime/testdata/conformance/binding_value_forward.sysml`, `binding_value_reverse.sysml`, `binding_nested_end.sysml`, `binding_nested_end_reverse.sysml`, `binding_expression_end.sysml`, `binding_multivalued.sysml`, `binding_calc_result.sysml`, `binding_calc_result_reverse.sysml`, `binding_object_end.sysml`, `binding_chained_object_ends.sysml`, `runtime/robustness_test.go` binding cases (including exact collection-conflict, multiple-contributor, element-budget and distinct-object cases) | ⚠️ Approximate (a flow between action nodes carries its value through lowering; a binding declared in a materialized type/usage body is lowered to a bidirectional runtime value identity, including inherited and nested ends, exact sequence/set equality, multiplicity handling, element-budget charging during propagation, lazy adoption of an unmaterialized composite endpoint by the read-side object — the end being read adopts an object the other end already holds rather than building a fresh one, so a chain of bindings through nested assemblies stays one object whichever end is read first —, conflict/cycle errors, and calc result binding. A binding that states only one end — `bind x;`, `binding bnd of x;` — lowers to no runtime binding (`binding bnd = x;` states two ends, `bnd` and `x`, per KerML.xtext BindingConnectorDeclaration), and bindings owned directly by packages/namespaces are not applied until namespace objects are materialized. Several bindings supplying unequal scalar values report a typed conflict; multiple bindings contributing to a multi-valued end are not yet element-wise merged and report a typed `ErrBindingEnd` instead of silently selecting one. **Adjudicated: the resolution half agrees with the reference** — `flow f from a.p.o to b.p.i;` with feature-chain ends is clean in the pinned validator and in ours (`passes/w6c_row_adjudication_test.go:TestW6CFlowChainEndsAndOccurrenceModifiers`); the residual is the runtime's binding merge, in `runtime/`, which this change did not own.) |
| An object of a connector usage holds the features it connects at its ends (`connection link : Link connect a.p to b.q` makes `link.source` **be** `a.p`), KerML 7.4.6, SysML v2 7.13.2 | `runtime/connector.go` `materializeConnectorFeatureValue`, `materializeConnector`, `attachConnectorEnd`, `bindEndFeatureValue`, `bindParticipants`, reached from `runtime/instance.go` `GetFeatureValue`; end features synthesized by `runtime/shape.go` `connectorEndFeatures`; attachments and effective end names by `semantics/connector.go` `Model.ConnectorObjectEnds`, `Model.IsConnectorObjectUsage`; inherited ends aliased by `runtime/subsetting.go` over `Model.ImplicitEndRedefinitions` | `connector_test.go` (`TestConnectorEndsAreTheConnectedFeatures`, `TestWritingAConnectedPortIsReadThroughTheEnd`, `TestConnectorEndFollowsAFeatureChain`, `TestConnectorEndAttachesToAPart`, `TestNaryConnectorKeepsEveryEnd`, `TestRedefinedEndSharesTheInheritedFeatureValue`, `TestEveryConnectorKindAttachesItsEnds`, `TestBindingConnectorIsAnObjectOfItsEnds`, `TestFlowConnectorIsAnObjectOfItsEnds`, `TestBindingConnectorEndFollowsAFeatureChain`, `TestBindingConnectorEndsHoldBoundValues`), `conformance/connector_end_identity.sysml`, `conformance/connector_object_binding_flow.sysml`, `semantics/connector_test.go:TestConnectorEndAttachments`, `semantics/connector_test.go:TestConnectorObjectEnds`, `robustness_connector_objects_test.go`, `robustness_test.go:unattachable_connector_end`, `multiplicity_on_a_connector`, `connector_attached_to_itself`, `mutually_attached_connectors` | ✅ Faithful (an end holds the very object the connector attaches to, so writing the connected port is read through the end and two connectors on different ports are distinguishable; the same object path now materializes connect, binding and non-message flow usages with declaration-order ends; message flows and one-ended bindings remain excluded; errors are typed `ErrConnectorEnd` with a source location and all-or-nothing rollback) |
| An untyped or anonymous connector usage materializes on the standard library base of its kind (`interface iface connect a.p to b.q;`, `connect a.p to b.q;`, `bind a.p = b.q;`, `flow a.p to b.q;`), SysML v2 7.13.2, 8.3.13 | `semantics/implicit.go` `implicitUsageBases`; `runtime/connector.go` `connectorBaseOf`, `anonymousConnectors`; `symbols/builder.go` `usageSymbolKind` (bindings use `SymbolBindingUsage`) and `semantics/shape.go` `IsShapeFeature` | `conformance/connector_end_identity.sysml`, `conformance/connector_object_binding_flow.sysml`, `connector_test.go` (`TestUntypedConnectorUsageMaterializes`, `TestAnonymousConnectorJoinsItsEnds`, `TestAnonymousConnectorIsMaterializedOnce`, `TestAnonymousBindingAndFlowAreOwnedConnectors`, `TestAnonymousSuccessionIsNoConnector`, `TestEveryConnectorKindAttachesItsEnds`), `parse/connection_implicit_type.golden` | ✅ Faithful (a connection, interface, allocation or connector usage, two-ended binding, or non-message flow that names no definition is an object of its kind's library base with its ends attached, named form and anonymous form alike, and an anonymous one materializes once per object; anonymous successions remain excluded) |
| A flow usage (`flow f from a.out to b.in`) and a binding usage (`binding b bind a.p = b.p`) are connector objects of the kernel layer, but state their ends in their own syntax — `Usage.FlowEnds`, and a binding's two `Usage.ConnectorEnds` — rather than in a `connect` clause | `parser/defusage.go` `parseFlowEnds` and `parseBindingDeclaration`/`parseBindingEnds`; `lower/connection.go` and `lower/binding.go` continue routing and binding semantics; `runtime/connector.go` `materializeConnectorFeatureValue`/`materializeConnectorAs`/`anonymousConnectors`; `semantics/connector.go` `Model.IsConnectorObjectUsage`/`Model.ConnectorObjectEnds` | `semantics/connector_test.go:TestConnectorObjectEnds`, `connector_test.go` (`TestBindingConnectorIsAnObjectOfItsEnds`, `TestFlowConnectorIsAnObjectOfItsEnds`, `TestAnonymousBindingAndFlowAreOwnedConnectors`), `robustness_connector_objects_test.go`, `routing_differential_test.go` | ✅ Faithful (the object model now materializes two-ended bindings and non-message flows through the same connector path as connect usages. `IsConnectorUsage` remains deliberately connect-only for views, document queries and passes; `IsConnectorObjectUsage` and `ConnectorObjectEnds` cover the wider object model. Flow objects do not yet hold their payload; message flows and one-ended bindings are not connector objects.) |
| The `of` clause of a binding (`binding b of full = level`) names the feature the binding binds, so it is a reference subsetting rather than a typing (KerML 8.3.3.3.9, SysML v2 8.3.13) | `parser/defusage.go` (`parseBindingEnds` reads what follows `of` as two `ast.ConnectorEnd`s, each attaching the feature it binds; the clause is never a typing) | `parser/binding_of_test.go:TestBindingOfTargetIsAReference`, `parse/constraint_parameterised_conditions.golden` | ✅ Faithful (formerly recorded as a typing, which reported the bound feature as "type must be a definition") |
| A declared name wins over an effective one in the same namespace (`part v { perform p; action p; }`) | `symbols/scope.go` `PreferDeclared`, used by `LookupLocal` and `resolve/qualified.go`'s segment walk; `symbols/builder.go` (`Symbol.EffectiveName`) | `semantics/reference_test.go` `TestReferenceFindsSiblingDeclaredAfterIt`, `TestQualifiedNameThroughEffectiveNameIsNotAmbiguous`, `TestRepeatedPerformResolvesToTheAction` | ✅ Faithful |
| `individual def X :> PartDef`, `x : IndividualDef` kind compatibility, SysML v2 7.9.4 | `passes/typecheck.go` `occurrenceDefSymbolKinds`/`isOccurrenceDefKind` (specialization) and `isCompatibleTyping` (typing) | `passes/typecheck_individuals_test.go`, corpus gate (`Verification Case Usage Example` now clean) | ✅ Faithful (an `individual def` is an occurrence definition, so it may specialize an occurrence definition of any kind and may type a usage wherever an occurrence definition may; specializing a data type — an attribute or enumeration definition — stays an error per 8.4.5.1, and a usage kind that rejects an occurrence definition, such as a port usage, still rejects an individual definition) |
Expand Down Expand Up @@ -2402,7 +2402,6 @@ The behavior-execution entries below — interruptible regions, expansion region
- Allocation execution - SysML v2 §9.2.4: syntax defined, execution semantics not normative

**Implementable But Not Yet Done:**
- Binding and flow connector objects (their ends reach routing, but neither is materialized as a connector object — see the Structural map)
- Interruptible regions (spec exists, needs token cancellation)
- Exception handlers (spec exists, needs exception propagation)

Expand Down
3 changes: 2 additions & 1 deletion internal/check/passes/typecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ func referentKind(sym *symbols.Symbol) symbols.SymbolKind {
}

// unclassifiedReferenceKindMessage judges a referent the builder leaves without a
// kind (a named binding): a feature of no constraint kind, named by its notation.
// kind: a feature of no constraint kind, named by its notation.
func unclassifiedReferenceKindMessage(decl declKind, rel ast.RelationshipKind, sym *symbols.Symbol) string {
return referentKindMessage(decl, rel, sym.Kind, sym.Notation())
}
Expand Down Expand Up @@ -989,6 +989,7 @@ var usageSymbolKinds = map[symbols.SymbolKind]bool{
symbols.SymbolRenderingUsage: true,
symbols.SymbolConcernUsage: true,
symbols.SymbolConnectionUsage: true,
symbols.SymbolBindingUsage: true,
symbols.SymbolSuccessionUsage: true,
symbols.SymbolFlowUsage: true,
symbols.SymbolPortUsage: true,
Expand Down
20 changes: 10 additions & 10 deletions internal/check/passes/typecheck_assert_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ part ctx { `
}
}

// A named binding is a feature the builder leaves unclassified, and a named
// transition is an action usage; neither is a constraint or a requirement.
// A named binding is a binding usage, and a named transition is an action usage;
// neither is a constraint or a requirement.
func TestAssertReferenceToBindingOrTransitionRejected(t *testing.T) {
prefix := `part def Holder {
attribute x; attribute y; binding b bind x = y; alias ab for b;
Expand All @@ -111,15 +111,15 @@ part ctx { `
tests := []struct {
name, target, want string
}{
{"binding", "assert b;", "assert target must be a constraint usage, found binding"},
{"negated binding", "assert not b;", "assert target must be a constraint usage, found binding"},
{"alias to binding", "assert ab;", "assert target must be a constraint usage, found binding"},
{"chained binding", "assert h.b;", "assert target must be a constraint usage, found binding"},
{"chained alias to binding", "assert h.ab;", "assert target must be a constraint usage, found binding"},
{"binding", "assert b;", "assert target must be a constraint usage, found bindingUsage"},
{"negated binding", "assert not b;", "assert target must be a constraint usage, found bindingUsage"},
{"alias to binding", "assert ab;", "assert target must be a constraint usage, found bindingUsage"},
{"chained binding", "assert h.b;", "assert target must be a constraint usage, found bindingUsage"},
{"chained alias to binding", "assert h.ab;", "assert target must be a constraint usage, found bindingUsage"},
{"chained transition", "assert h.sm.t;", "assert target must be a constraint usage, found actionUsage"},
{"binding in constraint body", "} constraint def K { assert b;", "assert target must be a constraint usage, found binding"},
{"satisfy binding", "satisfy b;", "satisfy target must be a requirement usage, found binding"},
{"satisfy chained binding", "satisfy h.b;", "satisfy target must be a requirement usage, found binding"},
{"binding in constraint body", "} constraint def K { assert b;", "assert target must be a constraint usage, found bindingUsage"},
{"satisfy binding", "satisfy b;", "satisfy target must be a requirement usage, found bindingUsage"},
{"satisfy chained binding", "satisfy h.b;", "satisfy target must be a requirement usage, found bindingUsage"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/runtime/adopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func carriedObject(v Value) (int64, bool) {
// connectorFeatureValue reports whether the feature value holds the object of a connector, whose
// ends a new context attaches again rather than keeping what they read before.
func (ctx *Context) connectorFeatureValue(s *FeatureValue) bool {
return s.Feature != nil && ctx.model.semantics.IsConnectorUsage(s.Feature.Symbol)
return s.Feature != nil && ctx.model.semantics.IsConnectorObjectUsage(s.Feature.Symbol)
}

// HoldsObject reports whether the value is, or carries, an object of this context:
Expand Down
Loading
Loading