Differences to Pilot Implementation
Note
Syside currently supports SysML v2 Standard Library 2025-07.
Syside tries to stay as truthful to the SysML v2 standard and, therefore, to Pilot Implementation. However, throughout the development process we identified some problems both in the standard and the Pilot Implementation. We have fixed some of these issues internally in Syside, and we are working on upstreaming the fixes to the standard. This section describes what fixes we have made to the standard library, and our reasoning behind the proposed changes. Additionally, we describe our extensions that we believe should be included in the standard.
Tip
You can find our changes to the standard library in GitHub here.
Type Checking
Currently, Pilot Implementation tries to perform type checking by looking at bound feature types symmetrically. However, there exists an asymmetry in assignments which requires that the assigned value conform to the feature it is being assigned to, and never vice versa. Furthermore, the specification itself completely omits type checking and type inference which are, in our opinion, crucial for determining model correctness and name resolution.
Consider
attribute num : ScalarValues::Positive = 5.0;
This is fine according to the Pilot implementation. The example above gives a type error
in Syside because assigning a rational to a positive is not guaranteed to satisfy type
constraints on num - rationals are strictly a superset of positives, meaning such
assignment will not always produce a positive value when evaluating num. This
completely breaks any expectations of statically typed variables (features).
Suppose num was declared without an assignment, and then some subclass of its owning
type redefined it with a value of = 5.0. Evaluating num on the derived subclass
would return 5.0 which is not positive, as advertised by the declaration.
For now
attribute def MyNumAttribute :> ScalarValues::Real;
attribute numAttribute : MyNumAttribute = new MyNumAttribute(5);
works to suppress type errors for subsetting scalar values. However, it is technically
not semantically correct, as the 5 redefines inherited self (by a coincidence no
less), meaning evaluation, if implemented for constructor expressions, would again
return a positive value of 5 and not MyNumAttribute. This is also how some of
the type errors were suppressed in the standard library that is bundled with Syside.
At the moment, Syside cannot infer the dimensions from expressions so every unit and quantity result type is up-casted to respective tensor types from the standard library. As a result, Syside cannot currently catch such errors as assigning length quantity to a mass quantity nor evaluate such expressions. We are working towards implementing this in the near future. However, this does still allow catching errors of assigning quantities to units, and vice versa, that were also found in the standard library.
Additionally:
Base::Anything::selfis checked as the scoping element, but name resolution still uses the declared type. This should really be a language keyword since expression evaluation uses it as such, even if extremely inefficiently compared to if it was a keyword.Base::things::thatis checked as the owning type of the scoping element, however similarly toselfname resolution uses the declared type. This should also be a language keyword, but expression evaluation has no special handler for it.Occurrences::Occurrence::thisis checked as eitherself,thatorthat.thisdepending on context. Similarly to those features, this should also be a language feature.
Also, note that:
Type checking is currently only enabled for
FeatureValues. Other places will be enabled in future updates, e.g. places where implicitBindingConnectorsare constructed by the Pilot implementation.EnumerationUsagesare currently problematic, the checker allows types that conform to the subclassified type of theirEnumerationDefinitions. This may be constrained to only allow this case in the body ofEnumerationDefinition, but this also needs checks for value compatibility in case of unnamedEnumerationUsages. Though we would rather require allEnumerationUsagesin the body ofEnumerationDefinitionto be named, so that they can be referenced in the interest of keeping implementation and usage simpler. In non-trivial projects, such unnamedEnumerationUsageswill only make it harder to reason about the model, as assignments to values typed by enumerations will introduce magic values. With multiple layers of subsettings, qualified usage ofEnumerationUsagesshould be the only way to go.
Type Inference
Similarly to type checking, specification does not mention type inference anywhere so Syside implements inference to match type checking:
Types of operators on arithmetic types (
Real,Rationaletc.) are inferred from their mathematical counterparts, e.g.Positive + Positive -> Positive, butPositive - Positive -> Integer.Constructor expressions are inferred to return the instantiated type.
ExpressionandFunctioninvocations are inferred to return their explicitly declaredreturnparameter, if any, otherwiseresultexpression type recursively.SelectExpressionsare inferred to return the first operand type, unless the select expression is a type guard expression, such as@@oristype. In that case the type is inferred to the type constrained by the select expression.CollectExpressionsare inferred to return their second operator return type (maportransformoperation in other languages).SequenceExpressions(operator,) are inferred to return the first non-null argument type. In a future update this should be corrected to return the most derived common base type(s) of all operands.ifand null-coalescing expressions are inferred similarly to sequence expressions.IndexExpressionsare inferred to return the type of their first operand since everything is a superposition ofnull, singular value and sequences at once. Technically, indexing a singularCollections::OrderedCollectionshould return the type redefiningCollections::Collection::elements, however this is statically ambiguous and prevents static type checking as its result type depends on runtime value:<T: Collections::OrderedCollection [1]>#(<int>) -> T.Collections::Collection::elements <T: Collections::Array [1]>#(<int...>) -> T.Collections::Collection::elements <T>#(<int>) -> T
Quantities and units are at the moment inferred to return corresponding tensor types from the standard library until dimension inference and arithmetic are implemented in Syside.
Standard library functions declaring operators are treated as corresponding operator expressions.
Some standard library functions are treated as generic, their results dependent on argument types, e.g.
CollectionFunctions::[head|tail|last]returning the feature of the first argument that redefinesCollections::Collection::elements.
Composite Features
According to the published specification, AttributeUsages and
AttributeDefinitions have validateAttributeUsageFeatures and
validateAttributeDefinitionFeatures constraints respectively, which require that all
features of Attributes must be non-composite. There were a few places in the
standard library and accompanying examples where Attributes contained owned
non-referential ItemUsages.
Function (Non-)Parameters
CollectionFunctions::'array#'::index is a function of 3 parameters, but only the
first was correctly marked with an in direction. The return feature below
invokes index with 3 positional parameters, yet Pilot implementation diagnoses
nothing. validateInvocationExpressionParameterRedefinition requires that all
InvocationExpression parameters redefine exactly one feature of the invoked
type, but implicit Expression owned feature redefinitions only work on
parameters, that is - directed features. Features with no direction are not considered
for implicit parameter redefinition, leaving the last 2 parameters without any
redefinitions.
Stricter Redefinition Hiding
Pilot Implementation and the specification hide any features that are redefined or redefine a previously hidden feature (e.g. a second inherited feature that redefines a feature from a common base type, which is also redefined by a previously traversed supertype). This is bad for multiple reasons:
It requires multiple pass computation for (inherited)
featuresand anything that subsets it - one pass to collect all inherited features without hiding any, and another to filter out hidden features. This is terrible for performance, especially for low-latency applications which try to resolve everything as lazily as possible.A solution using “aggressive caching” could be reached, however, we believe this would not be suitable for this particular problem. Currently, Pilot Implementation leads to cases such as:part def A { part a; } part def B :> A { part b :>> a; } part def C :> A { part c :>> a; } part def D :> B, C { part d :>> B::b; } part b = D().b; // reference error correctly part c = D().c; // reference resolves to C::c even though c should be hidden part d = D().d; // correctly resolves to D::d
The mentioned behavior breaks redefinition semantics. Consider
part def A { attribute a; } part def B0 :> A { attribute a :>> a; } part def B1 :> A { attribute a :>> a = 42; } part def C :> B0, B1 { attribute a :>> a = 7; }
Pilot implementation does not detect any issues, but the example above is semantically inconsistent:
Cconforms toB1, so features typed byCare implicitly up-castable to features typed byB1.B1::ahas a bound value so any features redefining it are forbidden from binding a new value (validateFeatureValueOverriding).C::ahas a different value thanB1::a.
Given feature b0 = C().a; and feature b1 = (C() as B1).a;, what would the
expected values of b0 and b1 be?
According to the current Pilot implementation, b0 == 7 and b1 == 42. However,
upholding validateFeatureValueOverriding, the expected values should be b0 == b1
== 42 and a semantic error of overriding feature value in C::a.
The current specification silently hides catastrophic diamond problem issues which could one day cause major issues during model execution, while also being difficult to debug.
Syside instead drops the implicit hiding of features and requires all features to explicitly redefine other features to hide them. This allows reference resolution to stay incremental without memory-intensive caching as features can only be hidden by features that are visited earlier in the hierarchy traversal. For now, this also catches the second problem in some cases when a redefining feature has the same name as the non-redefined feature.
Semantic Metadata baseType
Standard library package CauseAndEffect uses operator as to compute baseType
of SemanticMetadata. This actually performs a cast which, when used with a semantic
metatype, yields null value. We believe operator meta should have been used
instead, which performs cast on the metadata of its first operand that also includes the
semantic metatype. Syside diagnoses this during semantic resolution phase, while Pilot
implementation silently ignores this unintended behaviour. The result of this is that
CauseMetadata and EffectMetadata can be applied to any element type without
triggering diagnostics. Curiously, in the same package,
MulticausationSemanticMetadata and CausationSemanticMetadadata use meta
operator.
Other
Import filters have no effect currently.
SysML reflection is not implemented in the parser yet.
Some implicit model elements are not constructed, e.g. implicit binding connectors. This is done for performance reasons as they are not terribly useful for analysis but require a fairly sizeable increase in memory usage to accommodate. According to the specification, implicit binding connectors are constructed between every feature with value and value, even including literals, which in real models will be used a lot. As a result, strict type checking is not working for all cases yet, but will be implemented in a future update. A potential full fidelity flag may also be implemented for cases when users want those elements to be constructed.
Name resolution skips owning
Expressiontrees when resolving references in bodyExpressions, e.g.:function minimize { in collection; in expr fn; return : Base::Anything; } function MinimizeObjective { in alternatives; in expr fn; return feature = alternatives->minimize { in x; fn(x) }; }
Syside resolves the last
fntoMinimizeObjective::fn, whereas Pilot implementation does so to the bodyExpressionitself which redefinesminimize::fnimplicitly.Item flow source output and target input receive implicit directions of
outandinrespectively during semantic resolution, which lets Syside diagnose flows with incompatibleFeaturedirections throughvalidateRedefinitionDirectionConformance. KerML specification clause 7.4.10.3 seems to heavily imply flows from source output to target inputFeatures, hence this behaviour. However KerML 8.2.5.9.2 Note 1 seems to contradict this by specifying that directions should instead be set to the directions of the redefined features relative to its owningItemFlowEnd, which would makevalidateRedefinitionDirectionConformancepass unconditionally. A clarification on this is needed.Multiplicity bounds are parsed as arbitrary
Expressionsand are not bound by LL parser limitations. Pilot implementation has this marked with TODO.Named members in the root namespace are checked to be unique in the global namespace, that is, any
publicsymbols in all root namespaces must be unique. This is reported as a warning to match namespace distinguishability rule. The check is needed to avoid name lookup errors when one matching membership would be chosen in an unspecified manner. The specification does not determine which membership is chosen in such cases, (see note in clause 8.2.3.5.4 Full Resolution of Kernel Modeling Language, Release 2024-12). This may lead to silent breakages even in the same tool, e.g. if the order of documents changes between tool invocations. For a more in depth discussion of the issue and its consequences, please see the comments on OMG issue 43 (restricted to OMG members).