diff --git a/changes/unreleased/untyped-property-kind.fixed.md b/changes/unreleased/untyped-property-kind.fixed.md
new file mode 100644
index 0000000000..0c70f9a3ea
--- /dev/null
+++ b/changes/unreleased/untyped-property-kind.fixed.md
@@ -0,0 +1 @@
+- **An untyped v1 property is no longer always a `ref`.** The SysML v1 migrator wrote every property without a type as `ref x;`, ignoring MagicDraw's «ValueProperty», «PartProperty», «SharedProperty», «ReferenceProperty» and «ConstraintProperty» markers and then writing the marker as an applied stereotype. An untyped property now takes the usage its marker names (`attribute`, `part`, `ref`, `constraint`), with the marker omitted as it is on typed properties; without a marker it is a `part` when its aggregation is composite and an `attribute` otherwise, reported approximated. A property whose default is an individual stays a `ref` typed by it. A slot of such an untyped `part` holding an individual, which crashed the migration, is written as `individual part :>> x : 'the individual';`, checked against every type the part inherits when it redefines, subsets or shadows typed ones.
diff --git a/docs/project/sysml-v1-transformation-census-baseline.json b/docs/project/sysml-v1-transformation-census-baseline.json
index 9a2b875ded..7511d2c82d 100644
--- a/docs/project/sysml-v1-transformation-census-baseline.json
+++ b/docs/project/sysml-v1-transformation-census-baseline.json
@@ -248,7 +248,7 @@
},
"uml:Property": {
"pssm": 25,
- "fixtures": 468
+ "fixtures": 475
},
"uml:Property[qualifier]": {
"pssm": 0,
@@ -14643,14 +14643,16 @@
"filter"
],
"ocl": "sha256:99f06b2a46c64e98963563afeee2a1b8cb76a43e5fae2ee1af153c247800e6fc",
- "status": "faithful",
+ "status": "approximate",
"implementation": [
- "internal/translate/migrate/migrate.go:featureKeyword"
+ "internal/translate/migrate/migrate.go:featureKeyword",
+ "internal/translate/migrate/migrate.go:untypedKeyword"
],
"tests": [
- "tests/migrate/properties_common_test.go:TestUntypedPropertiesAreReferencesOutsideConstraintDefinitions"
+ "tests/migrate/properties_common_test.go:TestUntypedPropertiesAreAttributesInAndOutsideConstraintDefinitions",
+ "tests/migrate/relations_test.go:TestPropertyKindMarkersAreNotWritten"
],
- "reason": "an untyped property is written as a reference usage; the scope counts every uml:Property, so it is an upper bound",
+ "reason": "an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound",
"scope": [
"uml:Property"
]
diff --git a/docs/project/sysml-v1-transformation-census.md b/docs/project/sysml-v1-transformation-census.md
index 41dd426c23..15c8812dfb 100644
--- a/docs/project/sysml-v1-transformation-census.md
+++ b/docs/project/sysml-v1-transformation-census.md
@@ -34,7 +34,7 @@ that build one relationship of a target — share the verdict of the construct t
-**Census:** 783 mapping classes — 501 ✅ faithful, 49 ⚠️ approximate, 42 ❌ not implemented, 191 ⛔ deliberate, 0 🚧 known failure, 0 ❔ unknown.
+**Census:** 783 mapping classes — 500 ✅ faithful, 50 ⚠️ approximate, 42 ❌ not implemented, 191 ⛔ deliberate, 0 🚧 known failure, 0 ❔ unknown.
| Package | Mappings | ✅ | ⚠️ | ❌ | ⛔ | 🚧 | ❔ |
|---|---|---|---|---|---|---|---|
@@ -56,7 +56,7 @@ that build one relationship of a target — share the verdict of the construct t
| UML4SysML::Actions::StructuredActions | 3 | 2 | 1 | 0 | 0 | 0 | 0 |
| UML4SysML::Actions::VariableActions | 31 | 26 | 0 | 0 | 5 | 0 | 0 |
| UML4SysML::Activities | 68 | 60 | 0 | 0 | 8 | 0 | 0 |
-| UML4SysML::Classification | 48 | 43 | 0 | 0 | 5 | 0 | 0 |
+| UML4SysML::Classification | 48 | 42 | 1 | 0 | 5 | 0 | 0 |
| UML4SysML::CommonBehavior | 57 | 28 | 29 | 0 | 0 | 0 | 0 |
| UML4SysML::CommonStructure | 18 | 16 | 0 | 0 | 2 | 0 | 0 |
| UML4SysML::InformationFlows | 9 | 8 | 0 | 0 | 1 | 0 | 0 |
@@ -67,7 +67,7 @@ that build one relationship of a target — share the verdict of the construct t
| UML4SysML::StructuredClassifiers | 39 | 15 | 0 | 0 | 24 | 0 | 0 |
| UML4SysML::UseCases | 14 | 6 | 4 | 4 | 0 | 0 | 0 |
| UML4SysML::Values | 33 | 14 | 0 | 3 | 16 | 0 | 0 |
-| **Total** | 783 | 501 | 49 | 42 | 191 | 0 | 0 |
+| **Total** | 783 | 500 | 50 | 42 | 191 | 0 | 0 |
## Census rows
@@ -668,7 +668,7 @@ that build one relationship of a target — share the verdict of the construct t
| `PropertyCommon_Mapping` (abstract) | `Property` | `Feature` | `internal/translate/migrate/migrate.go:feature`
`internal/translate/migrate/migrate.go:featureRedefinitions`
`internal/translate/migrate/migrate.go:featureDefault`
`internal/translate/migrate/migrate.go:featureTyping`
`internal/translate/migrate/migrate.go:multiplicity` | `tests/migrate/properties_common_test.go:TestPropertyCommonPartsAreWrittenOnTheUsage` | ✅ faithful | a property's type, subsetted and redefined properties, default value and multiplicity are written on its usage; the scope counts every uml:Property, so it is an upper bound |
| `PropertySubsetting_Mapping` | `Property` | `Subsetting` | `internal/translate/migrate/translate.go:feature` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — |
| `PropertyTypedByClassInterface_Mapping` | `Property` | `OccurrenceUsage` | `internal/translate/migrate/migrate.go:featureKeyword`
`internal/translate/migrate/migrate.go:typeKeyword` | `internal/translate/migrate/occurrence_internal_test.go:TestPlainClassesAndTheirPropertiesUseOccurrenceForms`
`internal/translate/migrate/typefilter_internal_test.go:TestMetaclassFiltersExcludeNewConformingTypes` | ✅ faithful | a property typed by a plain class is written as an occurrence usage (`ref occurrence` unless composite); one typed by an interface as a port usage, the occurrence usage kind of the port def the interface becomes; the scope counts every uml:Property, so it is an upper bound |
-| `PropertyUntyped_Mapping` | `NamedElement` | `ReferenceUsage` | `internal/translate/migrate/migrate.go:featureKeyword` | `tests/migrate/properties_common_test.go:TestUntypedPropertiesAreReferencesOutsideConstraintDefinitions` | ✅ faithful | an untyped property is written as a reference usage; the scope counts every uml:Property, so it is an upper bound |
+| `PropertyUntyped_Mapping` | `NamedElement` | `ReferenceUsage` | `internal/translate/migrate/migrate.go:featureKeyword`
`internal/translate/migrate/migrate.go:untypedKeyword` | `tests/migrate/properties_common_test.go:TestUntypedPropertiesAreAttributesInAndOutsideConstraintDefinitions`
`tests/migrate/relations_test.go:TestPropertyKindMarkersAreNotWritten` | ⚠️ approximate | an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound |
| `Realization_Mapping` | `Realization` | `Dependency` | `internal/translate/migrate/migrate.go:dependency`
`internal/translate/migrate/realization.go:realization` | `tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — |
| `SlotFeatureTyping_Mapping` | `Slot` | `FeatureTyping` | `internal/translate/migrate/migrate.go:valueSlot`
`internal/translate/migrate/migrate.go:instanceSlot` | `tests/migrate/values_test.go:TestSlotContradictingItsFeatureIsUnmapped`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — |
| `SlotMembership_Mapping` | `Slot` | `FeatureMembership` | `internal/translate/migrate/migrate.go:valueSlot`
`internal/translate/migrate/migrate.go:instanceSlot` | `tests/migrate/values_test.go:TestSlotContradictingItsFeatureIsUnmapped`
`tests/migrate/migrate_test.go:TestGoldenNotation` | ✅ faithful | — |
@@ -1090,22 +1090,23 @@ SysML profile namespace. The counts are elements affected, not effort.
| Rank | OMG mappings | Status | Scope | PSSM suite | Fixtures | Total | Reason |
|---|---|---|---|---|---|---|---|
-| 1 | 2: `CommonPseudostate_Mapping`, `PseudoState_Mapping` | ⚠️ approximate | `uml:Pseudostate` | 289 | 71 | 360 | choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5) |
-| 2 | 1: `Region_Mapping` | ⚠️ approximate | `uml:Region` | 248 | 54 | 302 | the region of a single-region state is flattened into its state rather than written as a state usage; orthogonal regions become the substates of a parallel state; empty regions are not written |
-| 3 | 1: `OpaqueAction_Mapping` | ⚠️ approximate | `uml:OpaqueAction` | 0 | 158 | 158 | the body is translated into action statements when its language is read; otherwise the action is a stub keeping its pins (fixtures: 78 of 109 approximated) |
-| 4 | 3: `OpaqueBehaviorMembership_Mapping`, `OpaqueBehaviorSpecification_Mapping`, `OpaqueBehavior_Mapping` | ⚠️ approximate | `uml:OpaqueBehavior` | 0 | 49 | 49 | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) |
-| 5 | 4: `ChangeEventReturnParameterMembership_Mapping`, `ChangeEventReturnParameter_Mapping`, `ChangeEvent_Mapping`, `TimeEvent_Mapping` | ⚠️ approximate | `uml:TimeEvent`
`uml:ChangeEvent`
`uml:CallEvent` | 8 | 17 | 25 | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) |
-| 6 | 23: `TimeTriggerBindingConnector_Mapping`, `TimeTriggerCalculationUsage_Mapping`, `TimeTriggerEndFeatureMembership_Mapping`, `TimeTriggerEventChainingFeature_Mapping`, `TimeTriggerEventReturnParameterChainingFeature_Mapping`, `TimeTriggerExpressionFeatureTyping_Mapping`, `TimeTriggerExpressionFeatureValue_Mapping`, `TimeTriggerExpressionFeature_Mapping`, `TimeTriggerExpressionInvocationExpression_Mapping`, `TimeTriggerExpressionParameterMembership_Mapping`, `TimeTriggerFeatureMembership_Mapping`, `TimeTriggerFeatureTyping_Mapping`, `TimeTriggerFeatureValue_Mapping`, `TimeTriggerFeature_Mapping`, `TimeTriggerInvocationExpression_Mapping`, `TimeTriggerReferenceSubsetting_Mapping`, `TimeTriggerReferenceUsage2_Mapping`, `TimeTriggerReferenceUsage_Mapping`, `TimeTriggerReturnEndFeatureMembership_Mapping`, `TimeTriggerReturnParameterMembership_Mapping`, `TimeTriggerReturnParameter_Mapping`, `TimeTriggerReturnReferenceSubsetting_Mapping`, `TimeTriggerReturnReferenceUsage_Mapping` | ⚠️ approximate | `uml:TimeEvent` | 0 | 16 | 16 | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) |
-| 7 | 3: `EqualOperatorExpressionFeatureValue_Mapping`, `EqualOperatorExpressionFeature_Mapping`, `EqualOperatorExpressionOperandParameterMembership_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | these are used only by TIAOperatorExpression_Mapping, as the private first/second operands of a TestIdentityAction's `==`, not by Expression trees; they are written with TestIdentityAction, which is not migrated (PSSM: 14) |
-| 8 | 3: `TIAOperatorExpression_Mapping`, `TIAResultExpressionMembership_Mapping`, `TestIdentityAction_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | a TestIdentityAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
-| 9 | 9: `RICOAFeatureValueOperatorExpressionFeatureValue_Mapping`, `RICOAFeatureValueOperatorExpressionFeature_Mapping`, `RICOAFeatureValueOperatorExpression_Mapping`, `RICOAFeatureValueOperatorFeatureReferenceExpression_Mapping`, `RICOAFeatureValueOperatorMembership_Mapping`, `RICOAFeatureValueOperatorParameterMembership_Mapping`, `RICOAFeatureValue_Mapping`, `RICOAOutputPin_Mapping`, `ReadIsClassifiedObjectAction_Mapping` | ❌ not implemented | `uml:ReadIsClassifiedObjectAction` | 12 | 0 | 12 | a ReadIsClassifiedObjectAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
-| 10 | 4: `UseCaseEmptySubjectReferenceUsage_Mapping`, `UseCaseObjectiveMembership_Mapping`, `UseCaseObjectiveRequirementUsage_Mapping`, `UseCaseObjectiveSubjectMembership_Mapping` | ❌ not implemented | `uml:UseCase` | 0 | 12 | 12 | no objective is written for a use case: v1 has none to read, and the empty objective requirement the mapping creates is not written |
-| 11 | 1: `LoopNode_Mapping` | ⚠️ approximate | `uml:LoopNode` | 2 | 9 | 11 | a decider loop whose parts exchange values over pins the migrator writes is written as a v2 loop (the loops.xmi tested-first and post-tested fixtures): its loop variables and an `ended` flag as private features, its parts performing in the loop — test before body when tested first, else after — and its results read from the variables at the end; no corpus loop takes the loop form: PSSM's 2 fall back because their body outputs are structured-node output pins the migrator does not write, and TMT's 1 names no decider, so each keeps the body-once form and stays approximated per element; candidate erratum: the mapping gives only an ActionUsage and leaves its details undefined, so a loop's test and iteration are lost |
-| 12 | 1: `Actor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 8 | 8 | an actor is written as a part usage, not a part def: the actor lines of a use case diagram are connections, which join usages |
-| 13 | 3: `UseCaseActorFeatureTyping_Mapping`, `UseCaseActorMembership_Mapping`, `UseCaseActor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 8 | 8 | the use case owns no actor member: the association is written as a connection between the actor's part usage and the use case usage |
-| 14 | 2: `TestCaseActivityReturnParameterMembership_Mapping`, `TestCaseActivity_Mapping` | ⚠️ approximate | `sysml:TestCase` | 0 | 5 | 5 | the test case is written as a `verification def` whose objective verifies the requirements, but its behavior (activity or interaction) is not migrated |
-| 15 | 8: `ElementGroupMetadaMembership_Mapping`, `ElementGroupMetadataFeatureMembership_Mapping`, `ElementGroupMetadataFeatureTyping_Mapping`, `ElementGroupMetadataFeatureValue_Mapping`, `ElementGroupMetadataRedefinition_Mapping`, `ElementGroupMetadataReferenceUsage_Mapping`, `ElementGroupMetadataUsage_Mapping`, `ElementGroup_Mapping` | ❌ not implemented | `sysml:ElementGroup` | 0 | 0 | 0 | «ElementGroup» is not read by the migrator: the comment is written as a comment and no package grouping its members is written |
-| 16 | 8: `ProblemRationaleMetadataFeatureMembership_Mapping`, `ProblemRationaleMetadataFeatureTyping_Mapping`, `ProblemRationaleMetadataFeatureValue_Mapping`, `ProblemRationaleMetadataMembership_Mapping`, `ProblemRationaleMetadataRedefinition_Mapping`, `ProblemRationaleMetadataReferenceUsage_Mapping`, `ProblemRationaleMetadataUsage_Mapping`, `ProblemRationale_Mapping` | ❌ not implemented | `sysml:Problem`
`sysml:Rationale` | 0 | 0 | 0 | «Problem» and «Rationale» are not read by the migrator: the comment is written as a comment without metadata saying which it is |
-| 17 | 7: `REAFeatureValueOperatorExpressionFeatureTyping_Mapping`, `REAFeatureValueOperatorExpressionFeature_Mapping`, `REAFeatureValueOperatorExpressionMembership_Mapping`, `REAFeatureValueOperatorExpression_Mapping`, `REAFeatureValue_Mapping`, `REAOutputPin_Mapping`, `ReadExtentAction_Mapping` | ❌ not implemented | `uml:ReadExtentAction` | 0 | 0 | 0 | a ReadExtentAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
-| 18 | 8: `TraceAnnotation_Mapping`, `TraceMetadataFeatureMembership_Mapping`, `TraceMetadataReferenceUsageFeatureValue_Mapping`, `TraceMetadataReferenceUsageRedefinition_Mapping`, `TraceMetadataReferenceUsage_Mapping`, `TraceMetadataUsageFeatureTyping_Mapping`, `TraceMetadataUsage_Mapping`, `Trace_Mapping` | ⚠️ approximate | `sysml:Trace` | 0 | 0 | 0 | a «Trace» is written as a plain dependency with a `/* «Trace» */` comment, not as a dependency annotated by trace metadata |
+| 1 | 1: `PropertyUntyped_Mapping` | ⚠️ approximate | `uml:Property` | 25 | 475 | 500 | an untyped property is written as the usage its tool's property-kind marker names, as a `ref` typed by its default individual, else as a `part` when composite and an `attribute` otherwise, rather than always as a reference usage; the scope counts every uml:Property, so it is an upper bound |
+| 2 | 2: `CommonPseudostate_Mapping`, `PseudoState_Mapping` | ⚠️ approximate | `uml:Pseudostate` | 289 | 71 | 360 | choice and junction are written as state usages with StateMachines metadata, fork and join as nodes, history and terminate partially; a few pseudostates are unmapped (PSSM: 6, fixtures: 5) |
+| 3 | 1: `Region_Mapping` | ⚠️ approximate | `uml:Region` | 248 | 54 | 302 | the region of a single-region state is flattened into its state rather than written as a state usage; orthogonal regions become the substates of a parallel state; empty regions are not written |
+| 4 | 1: `OpaqueAction_Mapping` | ⚠️ approximate | `uml:OpaqueAction` | 0 | 158 | 158 | the body is translated into action statements when its language is read; otherwise the action is a stub keeping its pins (fixtures: 78 of 109 approximated) |
+| 5 | 3: `OpaqueBehaviorMembership_Mapping`, `OpaqueBehaviorSpecification_Mapping`, `OpaqueBehavior_Mapping` | ⚠️ approximate | `uml:OpaqueBehavior` | 0 | 49 | 49 | an opaque behavior is written as an action def whose body is the translated statements, or as a calc def when it computes a result; a body that does not translate is kept as a comment (fixtures: 43 of 47 approximated) |
+| 6 | 4: `ChangeEventReturnParameterMembership_Mapping`, `ChangeEventReturnParameter_Mapping`, `ChangeEvent_Mapping`, `TimeEvent_Mapping` | ⚠️ approximate | `uml:TimeEvent`
`uml:ChangeEvent`
`uml:CallEvent` | 8 | 17 | 25 | an event is not written as an element of its own: the trigger that refers to it is written inline as an accept; an event no trigger refers to is skipped, and a CallEvent is unmapped (PSSM: 8) |
+| 7 | 23: `TimeTriggerBindingConnector_Mapping`, `TimeTriggerCalculationUsage_Mapping`, `TimeTriggerEndFeatureMembership_Mapping`, `TimeTriggerEventChainingFeature_Mapping`, `TimeTriggerEventReturnParameterChainingFeature_Mapping`, `TimeTriggerExpressionFeatureTyping_Mapping`, `TimeTriggerExpressionFeatureValue_Mapping`, `TimeTriggerExpressionFeature_Mapping`, `TimeTriggerExpressionInvocationExpression_Mapping`, `TimeTriggerExpressionParameterMembership_Mapping`, `TimeTriggerFeatureMembership_Mapping`, `TimeTriggerFeatureTyping_Mapping`, `TimeTriggerFeatureValue_Mapping`, `TimeTriggerFeature_Mapping`, `TimeTriggerInvocationExpression_Mapping`, `TimeTriggerReferenceSubsetting_Mapping`, `TimeTriggerReferenceUsage2_Mapping`, `TimeTriggerReferenceUsage_Mapping`, `TimeTriggerReturnEndFeatureMembership_Mapping`, `TimeTriggerReturnParameterMembership_Mapping`, `TimeTriggerReturnParameter_Mapping`, `TimeTriggerReturnReferenceSubsetting_Mapping`, `TimeTriggerReturnReferenceUsage_Mapping` | ⚠️ approximate | `uml:TimeEvent` | 0 | 16 | 16 | a relative time event is written `accept after ` and an absolute one `accept at ` on the simulation clock; a time that is neither a number with a time unit nor an expression is unmapped (fixtures: 2 of 10 time events) |
+| 8 | 3: `EqualOperatorExpressionFeatureValue_Mapping`, `EqualOperatorExpressionFeature_Mapping`, `EqualOperatorExpressionOperandParameterMembership_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | these are used only by TIAOperatorExpression_Mapping, as the private first/second operands of a TestIdentityAction's `==`, not by Expression trees; they are written with TestIdentityAction, which is not migrated (PSSM: 14) |
+| 9 | 3: `TIAOperatorExpression_Mapping`, `TIAResultExpressionMembership_Mapping`, `TestIdentityAction_Mapping` | ❌ not implemented | `uml:TestIdentityAction` | 14 | 0 | 14 | a TestIdentityAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
+| 10 | 9: `RICOAFeatureValueOperatorExpressionFeatureValue_Mapping`, `RICOAFeatureValueOperatorExpressionFeature_Mapping`, `RICOAFeatureValueOperatorExpression_Mapping`, `RICOAFeatureValueOperatorFeatureReferenceExpression_Mapping`, `RICOAFeatureValueOperatorMembership_Mapping`, `RICOAFeatureValueOperatorParameterMembership_Mapping`, `RICOAFeatureValue_Mapping`, `RICOAOutputPin_Mapping`, `ReadIsClassifiedObjectAction_Mapping` | ❌ not implemented | `uml:ReadIsClassifiedObjectAction` | 12 | 0 | 12 | a ReadIsClassifiedObjectAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
+| 11 | 4: `UseCaseEmptySubjectReferenceUsage_Mapping`, `UseCaseObjectiveMembership_Mapping`, `UseCaseObjectiveRequirementUsage_Mapping`, `UseCaseObjectiveSubjectMembership_Mapping` | ❌ not implemented | `uml:UseCase` | 0 | 12 | 12 | no objective is written for a use case: v1 has none to read, and the empty objective requirement the mapping creates is not written |
+| 12 | 1: `LoopNode_Mapping` | ⚠️ approximate | `uml:LoopNode` | 2 | 9 | 11 | a decider loop whose parts exchange values over pins the migrator writes is written as a v2 loop (the loops.xmi tested-first and post-tested fixtures): its loop variables and an `ended` flag as private features, its parts performing in the loop — test before body when tested first, else after — and its results read from the variables at the end; no corpus loop takes the loop form: PSSM's 2 fall back because their body outputs are structured-node output pins the migrator does not write, and TMT's 1 names no decider, so each keeps the body-once form and stays approximated per element; candidate erratum: the mapping gives only an ActionUsage and leaves its details undefined, so a loop's test and iteration are lost |
+| 13 | 1: `Actor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 8 | 8 | an actor is written as a part usage, not a part def: the actor lines of a use case diagram are connections, which join usages |
+| 14 | 3: `UseCaseActorFeatureTyping_Mapping`, `UseCaseActorMembership_Mapping`, `UseCaseActor_Mapping` | ⚠️ approximate | `uml:Actor` | 0 | 8 | 8 | the use case owns no actor member: the association is written as a connection between the actor's part usage and the use case usage |
+| 15 | 2: `TestCaseActivityReturnParameterMembership_Mapping`, `TestCaseActivity_Mapping` | ⚠️ approximate | `sysml:TestCase` | 0 | 5 | 5 | the test case is written as a `verification def` whose objective verifies the requirements, but its behavior (activity or interaction) is not migrated |
+| 16 | 8: `ElementGroupMetadaMembership_Mapping`, `ElementGroupMetadataFeatureMembership_Mapping`, `ElementGroupMetadataFeatureTyping_Mapping`, `ElementGroupMetadataFeatureValue_Mapping`, `ElementGroupMetadataRedefinition_Mapping`, `ElementGroupMetadataReferenceUsage_Mapping`, `ElementGroupMetadataUsage_Mapping`, `ElementGroup_Mapping` | ❌ not implemented | `sysml:ElementGroup` | 0 | 0 | 0 | «ElementGroup» is not read by the migrator: the comment is written as a comment and no package grouping its members is written |
+| 17 | 8: `ProblemRationaleMetadataFeatureMembership_Mapping`, `ProblemRationaleMetadataFeatureTyping_Mapping`, `ProblemRationaleMetadataFeatureValue_Mapping`, `ProblemRationaleMetadataMembership_Mapping`, `ProblemRationaleMetadataRedefinition_Mapping`, `ProblemRationaleMetadataReferenceUsage_Mapping`, `ProblemRationaleMetadataUsage_Mapping`, `ProblemRationale_Mapping` | ❌ not implemented | `sysml:Problem`
`sysml:Rationale` | 0 | 0 | 0 | «Problem» and «Rationale» are not read by the migrator: the comment is written as a comment without metadata saying which it is |
+| 18 | 7: `REAFeatureValueOperatorExpressionFeatureTyping_Mapping`, `REAFeatureValueOperatorExpressionFeature_Mapping`, `REAFeatureValueOperatorExpressionMembership_Mapping`, `REAFeatureValueOperatorExpression_Mapping`, `REAFeatureValue_Mapping`, `REAOutputPin_Mapping`, `ReadExtentAction_Mapping` | ❌ not implemented | `uml:ReadExtentAction` | 0 | 0 | 0 | a ReadExtentAction is not migrated: it is written as a placeholder action that keeps its pins and position in the flow and is reported unmapped |
+| 19 | 8: `TraceAnnotation_Mapping`, `TraceMetadataFeatureMembership_Mapping`, `TraceMetadataReferenceUsageFeatureValue_Mapping`, `TraceMetadataReferenceUsageRedefinition_Mapping`, `TraceMetadataReferenceUsage_Mapping`, `TraceMetadataUsageFeatureTyping_Mapping`, `TraceMetadataUsage_Mapping`, `Trace_Mapping` | ⚠️ approximate | `sysml:Trace` | 0 | 0 | 0 | a «Trace» is written as a plain dependency with a `/* «Trace» */` comment, not as a dependency annotated by trace metadata |
diff --git a/docs/reference/sysml-v1-migration.md b/docs/reference/sysml-v1-migration.md
index 43d472a6a2..99d78f8e34 100644
--- a/docs/reference/sysml-v1-migration.md
+++ b/docs/reference/sysml-v1-migration.md
@@ -149,7 +149,9 @@ ties each of its mapping classes to the code that carries it out (or records why
| InstanceSpecification naming no classifier, under a `SimulationConfig`'s `resultLocation`, whose slots are of features of one lineage of blocks ending in the configuration's target classifier or a general of it (a simulation tool's result snapshot) | the `individual part def` of the most special of those blocks, with its slots; the note says which owner classified it and for which configuration | mapped |
| InstanceSpecification naming no classifier, anywhere else, or under a `resultLocation` with slots of features of blocks that are no one lineage or none the target is of | comment | **unmapped** — nothing classifies it; under a `resultLocation` the note says which owners its slots have and why they type no snapshot |
| Slot contradicting its feature (more values than the multiplicity allows, a repeated value of a feature written unique — declared so in v1, or written without its `nonunique` by the rule above — a feature of a classifier the instance is not written to specialize, an instance that is not of the property's type or of its default individual, a value outside the document) | comment | **unmapped** |
-| Slot of a port, or of an untyped property | comment (no individual can type a port; a `ref` without a type takes none) | **unmapped** |
+| Slot of a port | comment (no individual can type a port) | **unmapped** |
+| Property without a type | the usage MagicDraw's marker names — «ValueProperty» `attribute x;`, «PartProperty» `part x;`, «SharedProperty» or «ReferenceProperty» `ref x;`, «ConstraintProperty» `constraint x;` — the marker itself not written; one whose default is an individual, `ref x : 'the individual';` | mapped |
+| Property without a type or marker | `part x;` when its aggregation is composite, `attribute x;` otherwise | approximated |
| Property whose default is an InstanceSpecification of a block | the individual added to the usage's types, or its only type when the property is untyped; no `default` (a definition is not a v2 value). A port, a usage of another kind than the individual, or a usage whose type the individual is not an instance of, keeps its types and the default is a comment | approximated |
| Literal default on a value type with no scalar base (a structured value type, an enumeration) | comment | approximated |
| Real literal on an `Integer`/`Natural` feature, numeric string on a scalar feature | converted to the feature's scalar | mapped |
diff --git a/internal/translate/migrate/behavior_members_internal_test.go b/internal/translate/migrate/behavior_members_internal_test.go
index 486654d8b2..1d71131fac 100644
--- a/internal/translate/migrate/behavior_members_internal_test.go
+++ b/internal/translate/migrate/behavior_members_internal_test.go
@@ -71,14 +71,14 @@ func TestBehaviorMembersUnwritten(t *testing.T) {
`,
[]string{"_q"}, []string{"_r", "_p"}, 1,
- []string{"action def Run {\n view Rules {\n expose Run;\n expose p;\n render Views::asTextualNotation;\n }\n /* not migrated: Port 'q' — owned by a Activity, whose v2 body is its parameters and flow, not a place for a Port */\n ref p;\n assert constraint keep { true }\n}"}},
+ []string{"action def Run {\n view Rules {\n expose Run;\n expose p;\n render Views::asTextualNotation;\n }\n /* not migrated: Port 'q' — owned by a Activity, whose v2 body is its parameters and flow, not a place for a Port */\n attribute p;\n assert constraint keep { true }\n}"}},
{"a state machine writes its constraint and attribute, and has no place for a port",
`` + rule("_r", "keep") + `
`,
[]string{"_q"}, []string{"_r", "_p"}, 1,
- []string{"state def Modes {\n view Rules {\n expose Modes;\n expose p;\n render Views::asTextualNotation;\n }\n /* not migrated: Port 'q' — owned by a StateMachine, whose v2 body is its parameters and states, not a place for a Port */\n assert constraint keep { true }\n ref p;"}},
+ []string{"state def Modes {\n view Rules {\n expose Modes;\n expose p;\n render Views::asTextualNotation;\n }\n /* not migrated: Port 'q' — owned by a StateMachine, whose v2 body is its parameters and states, not a place for a Port */\n assert constraint keep { true }\n attribute p;"}},
} {
t.Run(tc.name, func(t *testing.T) {
shown := append(append([]string{"_b"}, tc.unwritten...), tc.written...)
diff --git a/internal/translate/migrate/diagrams_internal_test.go b/internal/translate/migrate/diagrams_internal_test.go
index dac4df4603..9ed5a6f58c 100644
--- a/internal/translate/migrate/diagrams_internal_test.go
+++ b/internal/translate/migrate/diagrams_internal_test.go
@@ -108,7 +108,7 @@ func TestDiagramViews(t *testing.T) {
[]string{"view Pumps {\n expose Pump;\n expose Sys::Pump::rate;\n render Views::asTreeDiagram;\n }"}, Mapped, ""},
{"a diagram of a block is written in its part def",
``, diagram("_d", "Pump IBD", "_pump", "SysML Internal Block Diagram", "_rate"),
- []string{"part def Pump {\n ref rate;\n view 'Pump IBD' {\n expose rate;\n render Views::asInterconnectionDiagram;\n }\n }"}, Mapped, ""},
+ []string{"part def Pump {\n attribute rate;\n view 'Pump IBD' {\n expose rate;\n render Views::asInterconnectionDiagram;\n }\n }"}, Mapped, ""},
{"a diagram naming no owner is written where its extension is held",
``, diagram("_d", "Loose", "", "Class Diagram", "_pump"),
[]string{"view Loose {\n expose Sys::Pump;\n render Views::asTreeDiagram;\n}"}, Approximated, "the diagram names no owner; written at the top level"},
@@ -183,7 +183,7 @@ func TestDiagramViews(t *testing.T) {
`,
diagram("_d", "Valves", "_sys", "SysML Block Definition Diagram", "_status"),
- []string{"action def Open {\n ref status;", "view Valves {\n expose Valve::Open::status;\n render Views::asTreeDiagram;\n }"}, Mapped, ""},
+ []string{"action def Open {\n attribute status;", "view Valves {\n expose Valve::Open::status;\n render Views::asTreeDiagram;\n }"}, Mapped, ""},
{"a diagram of a method activity named like one of its members is numbered, the member keeping its name",
`
@@ -195,7 +195,7 @@ func TestDiagramViews(t *testing.T) {
`,
diagram("_d1", "status", "_opening", "SysML Activity Diagram", "_status") + diagram("_d", "turn", "_opening", "SysML Activity Diagram", "_turn"),
- []string{"action def Open {\n view 'status 2' {\n expose Valve::Open::status;\n render Views::asTextualNotation;", "view 'turn 2' : StandardViewDefinitions::ActionFlowView {\n expose Open;\n render Views::asInterconnectionDiagram;", "ref status;", "action turn {"}, Approximated,
+ []string{"action def Open {\n view 'status 2' {\n expose Valve::Open::status;\n render Views::asTextualNotation;", "view 'turn 2' : StandardViewDefinitions::ActionFlowView {\n expose Open;\n render Views::asInterconnectionDiagram;", "attribute status;", "action turn {"}, Approximated,
"written as turn 2"},
{"a shown association end is exposed under the name its connection def declares",
`
diff --git a/internal/translate/migrate/migrate.go b/internal/translate/migrate/migrate.go
index d1cf950315..8f002086fa 100644
--- a/internal/translate/migrate/migrate.go
+++ b/internal/translate/migrate/migrate.go
@@ -1891,7 +1891,7 @@ func (m *migration) valueSlot(e, slot, f *sysmlv1.Element, dir string) ([]string
// instanceSlot resolves a slot holding instances: one redefines the feature
// typed by its individual; several each subset it under a redefinition counting them.
func (m *migration) instanceSlot(e, slot, f *sysmlv1.Element, kw, prefix string) ([]string, string, bool) {
- t := m.model.Ref(f, "type")
+ types := m.effectiveTypes(f)
var refs []string
for _, v := range slot.Owned("value") {
if v.Type != "InstanceValue" {
@@ -1913,8 +1913,11 @@ func (m *migration) instanceSlot(e, slot, f *sysmlv1.Element, kw, prefix string)
if kind == catNone || !individualTypes(kind, kw) {
return nil, slotValueSubject + describe(inst) + " is an " + individualKeyword(kind) + ", which cannot type " + article(kw) + kw, false
}
- if !m.instanceOf(classifiers, t) {
- return nil, slotValueSubject + describe(inst) + " is not an instance of " + qualifiedName(t) + ", the type of " + f.Name, false
+ // A property with no type, its own or inherited, takes any individual of its kind.
+ for _, t := range types {
+ if !m.instanceOf(classifiers, t) {
+ return nil, slotValueSubject + describe(inst) + " is not an instance of " + qualifiedName(t) + ", the type of " + f.Name, false
+ }
}
// The default individual types the property, so a slot can only repeat it.
if d, _ := m.typingIndividual(f, kw); d != nil && d != inst {
@@ -1948,6 +1951,39 @@ func (m *migration) instanceSlot(e, slot, f *sysmlv1.Element, kw, prefix string)
return lines, "", true
}
+// effectiveTypes are the types a property's usage must conform to: its own,
+// else those of every written property it redefines, subsets or shadows by name.
+func (m *migration) effectiveTypes(f *sysmlv1.Element) []*sysmlv1.Element {
+ var types []*sysmlv1.Element
+ seen := map[*sysmlv1.Element]bool{}
+ var walk func(*sysmlv1.Element)
+ walk = func(p *sysmlv1.Element) {
+ if p == nil || seen[p] {
+ return
+ }
+ seen[p] = true
+ if t := m.model.Ref(p, "type"); t != nil {
+ if !seen[t] {
+ seen[t] = true
+ types = append(types, t)
+ }
+ return
+ }
+ for _, role := range []string{"redefinedProperty", "subsettedProperty"} {
+ for _, r := range m.model.Refs(p, role) {
+ if m.written(r) {
+ walk(r)
+ }
+ }
+ }
+ if r, redefinable := m.shadowed(p); redefinable {
+ walk(r)
+ }
+ }
+ walk(f)
+ return types
+}
+
// article is the indefinite article before a word: "an item", "a part".
func article(word string) string {
if strings.ContainsRune("aeiou", rune(word[0])) {
@@ -2244,7 +2280,7 @@ func (m *migration) featureKeyword(p *sysmlv1.Element, owner category) (keyword,
return "port", "", ""
}
if t == nil {
- return "ref", "", "the untyped property is written as a reference usage"
+ return m.untypedKeyword(p)
}
if owner == catUseCase && t.Type == "Actor" && m.written(t) {
return "actor", "", ""
@@ -2288,6 +2324,22 @@ func (m *migration) featureKeyword(p *sysmlv1.Element, owner category) (keyword,
return kw, "", note
}
+// untypedKeyword is the usage an untyped property is written as: the kind its
+// tool's marker gives it, a ref its default individual can type, else a part
+// when composite and an attribute otherwise.
+func (m *migration) untypedKeyword(p *sysmlv1.Element) (keyword, prefix, note string) {
+ if kind := m.markedPropertyKind(p); kind != "" {
+ return kind, "", ""
+ }
+ if m.defaultIndividual(p) != nil {
+ return "ref", "", ""
+ }
+ if p.Attrs["aggregation"] == "composite" {
+ return "part", "", "the untyped composite property is written as a part"
+ }
+ return "attribute", "", "the untyped property is written as an attribute"
+}
+
// typeKeyword is the usage keyword a property takes from its type alone,
// before its owner and aggregation weigh in.
func (m *migration) typeKeyword(t *sysmlv1.Element) (keyword, note string) {
@@ -4238,9 +4290,8 @@ var propertyKindMarkers = map[string]string{
// isPropertyKindMarker recognises MagicDraw's marker of a property's kind, which
// the usage's keyword already says; a same-named stereotype from elsewhere is kept.
func (m *migration) isPropertyKindMarker(e *sysmlv1.Element, s *sysmlv1.Stereotype) bool {
- kind, ok := propertyKindMarkers[s.Name]
- if !ok || len(s.Tags) > 0 || e.Type != "Property" || e.Parent == nil ||
- !isMagicDrawCustomization(s.Namespace) {
+ kind := propertyKindMarker(e, s)
+ if kind == "" || e.Parent == nil {
return false
}
owner, _ := m.classify(e.Parent)
@@ -4251,6 +4302,26 @@ func (m *migration) isPropertyKindMarker(e *sysmlv1.Element, s *sysmlv1.Stereoty
return usageKind(kw, prefix) == kind
}
+// propertyKindMarker is the kind of usage MagicDraw's marker s says property e
+// is, or empty when s is not such a marker.
+func propertyKindMarker(e *sysmlv1.Element, s *sysmlv1.Stereotype) string {
+ kind, ok := propertyKindMarkers[s.Name]
+ if !ok || len(s.Tags) > 0 || e.Type != "Property" || !isMagicDrawCustomization(s.Namespace) {
+ return ""
+ }
+ return kind
+}
+
+// markedPropertyKind is the kind MagicDraw's marker on property e gives it, if any.
+func (m *migration) markedPropertyKind(e *sysmlv1.Element) string {
+ for _, s := range e.Stereotypes {
+ if kind := propertyKindMarker(e, s); kind != "" {
+ return kind
+ }
+ }
+ return ""
+}
+
// usageKind is the kind of property a usage keyword and its `ref ` prefix express.
func usageKind(kw, prefix string) string {
switch {
diff --git a/tests/migrate/properties_common_test.go b/tests/migrate/properties_common_test.go
index e493e5b0fa..e37601ee36 100644
--- a/tests/migrate/properties_common_test.go
+++ b/tests/migrate/properties_common_test.go
@@ -29,7 +29,7 @@ func TestPropertyCommonPartsAreWrittenOnTheUsage(t *testing.T) {
}
}
-func TestUntypedPropertiesAreReferencesOutsideConstraintDefinitions(t *testing.T) {
+func TestUntypedPropertiesAreAttributesInAndOutsideConstraintDefinitions(t *testing.T) {
r := migrateDocument(t, `
@@ -39,6 +39,6 @@ func TestUntypedPropertiesAreReferencesOutsideConstraintDefinitions(t *testing.T
`,
``)
wantClean(t, "property_untyped.sysml", r)
- wantLine(t, r.Notation, "ref plain;")
+ wantLine(t, r.Notation, "attribute plain;")
wantLine(t, r.Notation, "in attribute parameter[1];")
}
diff --git a/tests/migrate/relations_test.go b/tests/migrate/relations_test.go
index 470d97942c..6fa76e4ac1 100644
--- a/tests/migrate/relations_test.go
+++ b/tests/migrate/relations_test.go
@@ -377,10 +377,10 @@ func TestUnwritableFeaturePartsAreDroppedWithNotes(t *testing.T) {
`, `
`)
wantLine(t, r.Notation, "ref intro;")
- wantLine(t, r.Notation, "ref grid;")
- wantLine(t, r.Notation, "ref many[0..*];")
- wantLine(t, r.Notation, "ref margin {")
- wantLine(t, r.Notation, `ref exposure default = "4";`)
+ wantLine(t, r.Notation, "attribute grid;")
+ wantLine(t, r.Notation, "attribute many[0..*];")
+ wantLine(t, r.Notation, "attribute margin {")
+ wantLine(t, r.Notation, `attribute exposure default = "4";`)
if strings.Contains(string(r.Notation), "= NaN") {
t.Errorf("a non-finite real was written:\n%s", r.Notation)
}
@@ -426,9 +426,9 @@ func TestClashingSiblingNamesAreDistinguished(t *testing.T) {
`)
- wantLine(t, r.Notation, "ref unnamed1;")
- wantLine(t, r.Notation, "ref 'unnamed1 2';")
- wantLine(t, r.Notation, "ref q :>> 'unnamed1 2';")
+ wantLine(t, r.Notation, "attribute unnamed1;")
+ wantLine(t, r.Notation, "attribute 'unnamed1 2';")
+ wantLine(t, r.Notation, "attribute q :>> 'unnamed1 2';")
wantLine(t, r.Notation, "part def 'C 2'")
wantLine(t, r.Notation, "part x : 'C 2';")
for id, want := range map[string]string{
@@ -501,8 +501,8 @@ func TestExternalSpecializationsAreNotWritten(t *testing.T) {
`, `
`)
- wantLine(t, r.Notation, "ref a;")
- wantLine(t, r.Notation, "ref b;")
+ wantLine(t, r.Notation, "attribute a;")
+ wantLine(t, r.Notation, "attribute b;")
if strings.Contains(string(r.Notation), "_ext") || strings.Contains(string(r.Notation), "unnamed") {
t.Errorf("notation refers to an external feature:\n%s", r.Notation)
}
@@ -1310,10 +1310,19 @@ func TestConstraintParametersStoredAsPortsAreInParameters(t *testing.T) {
// MagicDraw's tagless property-kind markers say what the usage keyword says,
// so they are not written; a same-named stereotype from any other profile, a
-// marker carrying a tag, and one on a property of another kind are kept.
+// marker carrying a tag, and one on a property of another kind are kept. An
+// untyped property takes its marker's kind; without one it is an attribute,
+// or a part when composite.
func TestPropertyKindMarkersAreNotWritten(t *testing.T) {
r := migrateFixtureFile(t, "property_markers")
for _, line := range []string{
+ "attribute load;",
+ "part trailer;",
+ "ref depot;",
+ "ref driver;",
+ "constraint rule;",
+ "attribute color;",
+ "part chassis;",
"attribute mass : ScalarValues::Real;",
"attribute mode : Mode;",
"part engine : Engine;",
@@ -1341,11 +1350,14 @@ func TestPropertyKindMarkersAreNotWritten(t *testing.T) {
t.Errorf("«%s» written %d times, want %d", name, n, want)
}
}
- for _, id := range []string{"_mass", "_mode", "_engine", "_lead", "_limit", "_ms_v", "_ms_wheel", "_ms_hub", "_ms_k", "_odd", "_axle", "_cabin", "_serial"} {
+ for _, id := range []string{"_mass", "_mode", "_engine", "_lead", "_limit", "_ms_v", "_ms_wheel", "_ms_hub", "_ms_k", "_odd", "_axle", "_cabin", "_serial",
+ "_load", "_trailer", "_depot", "_driver", "_rule"} {
if es := entriesFor(r, id); len(es) != 1 || es[0].Verdict != migrate.Mapped {
t.Errorf("%s entries = %+v", id, es)
}
}
+ wantNote(t, r, "_color", migrate.Approximated, "the untyped property is written as an attribute")
+ wantNote(t, r, "_chassis", migrate.Approximated, "the untyped composite property is written as a part")
if es := entriesFor(r, "_spare"); len(es) != 1 || es[0].Verdict != migrate.Approximated || !strings.Contains(es[0].Note, "shared aggregation") {
t.Errorf("_spare entries = %+v", es)
}
diff --git a/tests/migrate/testdata/xmi/collectors.golden.report.txt b/tests/migrate/testdata/xmi/collectors.golden.report.txt
index 35ff1e3cbf..651e6819d5 100644
--- a/tests/migrate/testdata/xmi/collectors.golden.report.txt
+++ b/tests/migrate/testdata/xmi/collectors.golden.report.txt
@@ -17,7 +17,7 @@
Diagram Sketches::Sketch _diag_untyped -> Sketches::Sketch (a diagram of unknown kind written as a view rendered asTextualNotation; no diagram representation is serialized: what the diagram is and shows is unknown, and the view exposes nothing)
Diagram Structure::Crane Structure _diag_structure -> Structure::'Crane Structure' (a SysML Block Definition Diagram written as a view rendered asTreeDiagram; 2 of 7 shown elements are not written and not exposed; 3 of 7 shown ids resolve to no element)
Property Structure::Crane::cable _prop_cable -> Structure::Crane::cable (shared aggregation is written as a reference part)
-Property Structure::Crane::counterweight _prop_counterweight -> Structure::Crane::counterweight (the untyped property is written as a reference usage)
+Property Structure::Crane::counterweight _prop_counterweight -> Structure::Crane::counterweight (the untyped composite property is written as a part)
«Document» Class Yard Handbook::Yard Handbook _st_doc_handbook -> part def 'Yard Handbook'::'Yard Handbook Document' (the «Document» is written as a Document definition of 24 section(s); reads the 7 elements the tool lists as used on the SysML Block Definition Diagram 'Crane Structure', whose symbols are not serialized; the list need not be all it shows; leaves out 2 elements shown on the SysML Block Definition Diagram 'Crane Structure' that the archive does not describe; leaves out 1 element shown on the SysML Block Definition Diagram 'Crane Structure' that the migration does not write; leaves out 1 element shown on the SysML Block Definition Diagram 'Crane Structure' written within the elements owning them, with no v2 element of their own; «FilterByNames» Yard Viewpoints::Optional Drafts Viewpoint::Optional Drafts Method::Filter By Names names the elements it keeps, since a WhereName query matches v2 names: the «Block» Class Drafts:: is named unnamed in v2)
Activity Yard Viewpoints::All Diagrams Viewpoint::All Diagrams Method _act_all -> 'Yard Viewpoints'::'All Diagrams Viewpoint'::'All Diagrams Method' (the classifier behavior is run by every object of Yard Viewpoints::All Diagrams Viewpoint as its usage all Diagrams Method)
«FilterByDiagramType» CallBehaviorAction Yard Viewpoints::All Diagrams Viewpoint::All Diagrams Method::Filter By Diagram Type _all_filter -> 'Filter By Diagram Type' (a step with no behavior and no duration; it passes the token on)
diff --git a/tests/migrate/testdata/xmi/collectors.golden.sysml b/tests/migrate/testdata/xmi/collectors.golden.sysml
index 57a2f6ce3a..a1981df2e0 100644
--- a/tests/migrate/testdata/xmi/collectors.golden.sysml
+++ b/tests/migrate/testdata/xmi/collectors.golden.sysml
@@ -4,7 +4,7 @@ package Structure {
part hook : Hook;
ref part cable : Cable;
ref part operator : Operator;
- ref counterweight;
+ part counterweight;
connect hook to cable;
view 'Crane Internals' {
expose hook;
diff --git a/tests/migrate/testdata/xmi/property_markers.golden.report.txt b/tests/migrate/testdata/xmi/property_markers.golden.report.txt
index 48c0e05a46..a42bf03af7 100644
--- a/tests/migrate/testdata/xmi/property_markers.golden.report.txt
+++ b/tests/migrate/testdata/xmi/property_markers.golden.report.txt
@@ -1,12 +1,14 @@
# SysML v1 to v2 migration report: property_markers.xmi
# exported by Example UML Tool
-# migrated 26 element(s): 24 mapped, 2 approximated, 0 unmapped (2 skipped as profile, library or notation-only content, 0 as model elements nothing refers to)
+# migrated 33 element(s): 29 mapped, 4 approximated, 0 unmapped (2 skipped as profile, library or notation-only content, 0 as model elements nothing refers to)
-## approximated (2)
+## approximated (4)
Constraint Fleet::MaxSpeed:: _ms_rule -> Fleet::MaxSpeed (opaque expression copied verbatim (language SysML))
+Property Fleet::Vehicle::chassis _chassis -> Fleet::Vehicle::chassis (the untyped composite property is written as a part)
+Property Fleet::Vehicle::color _color -> Fleet::Vehicle::color (the untyped property is written as an attribute)
«SharedProperty» Property Fleet::Vehicle::spare _spare -> Fleet::Vehicle::spare (shared aggregation is written as a reference part)
-## mapped (24)
+## mapped (29)
Package Fleet _sys -> Fleet
«Block» Class Fleet::Engine _engine_def -> Fleet::Engine
«ConstraintBlock» Class Fleet::MaxSpeed _maxspeed -> Fleet::MaxSpeed
@@ -20,13 +22,18 @@ EnumerationLiteral Fleet::Mode::sport _modes_sport -> Fleet::Mode::sport
«Block» Class Fleet::Vehicle _vehicle -> Fleet::Vehicle
«PartProperty» Property Fleet::Vehicle::axle _axle -> Fleet::Vehicle::axle
«PartProperty» Property Fleet::Vehicle::cabin _cabin -> Fleet::Vehicle::cabin
+«SharedProperty» Property Fleet::Vehicle::depot _depot -> Fleet::Vehicle::depot
+«ReferenceProperty» Property Fleet::Vehicle::driver _driver -> Fleet::Vehicle::driver
«PartProperty» Property Fleet::Vehicle::engine _engine -> Fleet::Vehicle::engine
«ReferenceProperty» Property Fleet::Vehicle::lead _lead -> Fleet::Vehicle::lead
«ConstraintProperty» Property Fleet::Vehicle::limit _limit -> Fleet::Vehicle::limit
+«ValueProperty» Property Fleet::Vehicle::load _load -> Fleet::Vehicle::load
«ValueProperty» Property Fleet::Vehicle::mass _mass -> Fleet::Vehicle::mass
«ValueProperty» Property Fleet::Vehicle::mode _mode -> Fleet::Vehicle::mode
«ValueProperty» Property Fleet::Vehicle::odd _odd -> Fleet::Vehicle::odd
+«ConstraintProperty» Property Fleet::Vehicle::rule _rule -> Fleet::Vehicle::rule
«ValueProperty» Property Fleet::Vehicle::serial _serial -> Fleet::Vehicle::serial («ValueProperty» from http://example.com/legacy/customization is applied from a profile the document does not define; the application is kept as a comment)
+«PartProperty» Property Fleet::Vehicle::trailer _trailer -> Fleet::Vehicle::trailer
«Block» Class Fleet::Wheel _wheel -> Fleet::Wheel
Model Model _m (the root model's members are written at the top level)
Profile Vehicle Profile _vp -> 'Vehicle Profile'
diff --git a/tests/migrate/testdata/xmi/property_markers.golden.sysml b/tests/migrate/testdata/xmi/property_markers.golden.sysml
index 08deb2f184..feb122da83 100644
--- a/tests/migrate/testdata/xmi/property_markers.golden.sysml
+++ b/tests/migrate/testdata/xmi/property_markers.golden.sysml
@@ -18,6 +18,13 @@ package Fleet {
part cabin : Engine {
@'Vehicle Profile'::PartProperty;
}
+ attribute load;
+ part trailer;
+ ref depot;
+ ref driver;
+ constraint rule;
+ attribute color;
+ part chassis;
attribute serial : ScalarValues::String {
/* applied stereotype «ValueProperty» */
}
diff --git a/tests/migrate/testdata/xmi/property_markers.xmi b/tests/migrate/testdata/xmi/property_markers.xmi
index f9ce60a928..154b0a5149 100644
--- a/tests/migrate/testdata/xmi/property_markers.xmi
+++ b/tests/migrate/testdata/xmi/property_markers.xmi
@@ -30,6 +30,13 @@
+
+
+
+
+
+
+
@@ -74,6 +81,11 @@
+
+
+
+
+
diff --git a/tests/migrate/testdata/xmi/tool_profiles.golden.report.txt b/tests/migrate/testdata/xmi/tool_profiles.golden.report.txt
index e393dd6084..1f964b1c7b 100644
--- a/tests/migrate/testdata/xmi/tool_profiles.golden.report.txt
+++ b/tests/migrate/testdata/xmi/tool_profiles.golden.report.txt
@@ -4,9 +4,9 @@
## approximated (4)
StateMachine Mockups::Clock::Ticking _ui_clock_sm -> Mockups::Clock::Ticking (the classifier behavior is run by every object of Mockups::Clock as its usage ticking)
-Property Mockups::Clock::ticks _ui_clock_ticks -> Mockups::Clock::ticks (the untyped property is written as a reference usage)
+Property Mockups::Clock::ticks _ui_clock_ticks -> Mockups::Clock::ticks (the untyped property is written as an attribute)
«SimulationConfig» Class Runs::Run pump _cfg -> Runs::'Run pump' (neither System::Pump nor any general of it has a classifier behavior, so the configuration only holds 'Pump')
-Property System::Pump::mass _pump_mass -> System::Pump::mass (the untyped property is written as a reference usage)
+Property System::Pump::mass _pump_mass -> System::Pump::mass (the untyped property is written as an attribute)
## mapped (15)
Package Customizations _cust -> Customizations
diff --git a/tests/migrate/testdata/xmi/tool_profiles.golden.sysml b/tests/migrate/testdata/xmi/tool_profiles.golden.sysml
index d34f07ae5c..ff9b5b512b 100644
--- a/tests/migrate/testdata/xmi/tool_profiles.golden.sysml
+++ b/tests/migrate/testdata/xmi/tool_profiles.golden.sysml
@@ -5,7 +5,7 @@ package 'My Profile' {
}
package System {
part def Pump {
- ref mass;
+ attribute mass;
@'My Profile'::Tracked {
ticket = "T-42";
}
@@ -14,7 +14,7 @@ package System {
package Customizations;
package Mockups {
occurrence def Clock {
- ref ticks;
+ attribute ticks;
state def Ticking {
entry; then Running;
state Running;
diff --git a/tests/migrate/values_test.go b/tests/migrate/values_test.go
index 17827db813..415783f2bf 100644
--- a/tests/migrate/values_test.go
+++ b/tests/migrate/values_test.go
@@ -792,11 +792,114 @@ func TestPartSlotWithoutAConformingIndividualIsUnmapped(t *testing.T) {
wantNote(t, r, "_sl3", migrate.Unmapped, "the slot's value 'other 1' is not an instance of MCS, the type of mcs")
wantNote(t, r, "_sl4", migrate.Unmapped, "the slot of port bus is not written: v2 has no individual port for it to be typed by")
wantNote(t, r, "_sl5", migrate.Unmapped, "the slot of port link is not written: v2 has no individual port for it to be typed by")
- wantNote(t, r, "_sl6", migrate.Unmapped, "the slot of loose is not written: the property is written as a plain ref, which cannot be typed by an individual")
+ wantNote(t, r, "_sl6", migrate.Unmapped, "the individual mcs 1 is a definition, which is not a v2 value")
wantNote(t, r, "_sl7", migrate.Unmapped, "the slot's value 'other 1' is an individual part def, which cannot type an item")
wantClean(t, "bad-part-slots.sysml", r)
}
+// A slot of an untyped part — composite, or marked «PartProperty» — holds any
+// individual part: the property has no type to check the instance against.
+func TestUntypedPartSlotIsTypedByItsIndividual(t *testing.T) {
+ r := migrateDocument(t, `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, `
+
+
+ `)
+ wantLine(t, r.Notation, "part wheel;")
+ wantLine(t, r.Notation, "part spare;")
+ wantLine(t, r.Notation, "individual part :>> wheel : 'wheel 1';")
+ wantLine(t, r.Notation, "individual part :>> spare : 'wheel 1';")
+ wantNote(t, r, "_sw", migrate.Mapped, "")
+ wantNote(t, r, "_ss", migrate.Mapped, "")
+ wantClean(t, "untyped-part-slots.sysml", r)
+}
+
+// An untyped part that redefines a typed one, by declaration or by sharing its
+// name, inherits the type: a slot holding an individual of another type is unmapped.
+func TestUntypedRedefiningPartSlotKeepsTheInheritedType(t *testing.T) {
+ r := migrateDocument(t, `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, `
+
+
+
+ `)
+ wantLine(t, r.Notation, "part wheel :>> wheel;")
+ wantLine(t, r.Notation, "part front :>> spare;")
+ wantNoLine(t, r.Notation, "individual part :>> wheel : 'engine 1';")
+ wantNote(t, r, "_sw", migrate.Unmapped, "the slot's value 'engine 1' is not an instance of Wheel, the type of wheel")
+ wantLine(t, r.Notation, "individual part :>> front : 'wheel 1';")
+ wantNote(t, r, "_ss", migrate.Mapped, "")
+ wantClean(t, "redefining-part-slots.sysml", r)
+}
+
+// An untyped part subsetting several typed ones must hold an instance of every
+// type: an individual of only the first is unmapped, one of all is written.
+func TestUntypedPartSlotKeepsEveryInheritedType(t *testing.T) {
+ r := migrateDocument(t, `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, `
+
+
+ `)
+ wantLine(t, r.Notation, "part spare :> vehicle :> wheel;")
+ wantNoLine(t, r.Notation, "individual part :>> spare : 'vehicle 1';")
+ wantNote(t, r, "_ss", migrate.Unmapped, "the slot's value 'vehicle 1' is not an instance of Wheel, the type of spare")
+ wantLine(t, r.Notation, "individual part :>> trailer : 'wheel 1';")
+ wantNote(t, r, "_st", migrate.Mapped, "")
+ wantClean(t, "subsetting-part-slots.sysml", r)
+}
+
// An individual takes the kind of its classifier — `individual constraint
// def` for an instance of a constraint block — and its slots redefine the
// parameters with their `in` direction. Classifiers of another kind are not