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::self is 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::that is checked as the owning type of the scoping element, however similarly to self name resolution uses the declared type. This should also be a language keyword, but expression evaluation has no special handler for it.

  • Occurrences::Occurrence::this is checked as either self, that or that.this depending 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 implicit BindingConnectors are constructed by the Pilot implementation.

  • EnumerationUsages are currently problematic, the checker allows types that conform to the subclassified type of their EnumerationDefinitions. This may be constrained to only allow this case in the body of EnumerationDefinition, but this also needs checks for value compatibility in case of unnamed EnumerationUsages. Though we would rather require all EnumerationUsages in the body of EnumerationDefinition to be named, so that they can be referenced in the interest of keeping implementation and usage simpler. In non-trivial projects, such unnamed EnumerationUsages will 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 of EnumerationUsages should 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, Rational etc.) are inferred from their mathematical counterparts, e.g. Positive + Positive -> Positive, but Positive - Positive -> Integer.

  • Constructor expressions are inferred to return the instantiated type.

  • Expression and Function invocations are inferred to return their explicitly declared return parameter, if any, otherwise result expression type recursively.

  • SelectExpressions are inferred to return the first operand type, unless the select expression is a type guard expression, such as @@ or istype. In that case the type is inferred to the type constrained by the select expression.

  • CollectExpressions are inferred to return their second operator return type (map or transform operation 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.

  • if and null-coalescing expressions are inferred similarly to sequence expressions.

  • IndexExpressions are inferred to return the type of their first operand since everything is a superposition of null, singular value and sequences at once. Technically, indexing a singular Collections::OrderedCollection should return the type redefining Collections::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 redefines Collections::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) features and 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:

  1. C conforms to B1, so features typed by C are implicitly up-castable to features typed by B1.

  2. B1::a has a bound value so any features redefining it are forbidden from binding a new value (validateFeatureValueOverriding).

  3. C::a has a different value than B1::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 Expression trees when resolving references in body Expressions, 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 fn to MinimizeObjective::fn, whereas Pilot implementation does so to the body Expression itself which redefines minimize::fn implicitly.

  • Item flow source output and target input receive implicit directions of out and in respectively during semantic resolution, which lets Syside diagnose flows with incompatible Feature directions through validateRedefinitionDirectionConformance. KerML specification clause 7.4.10.3 seems to heavily imply flows from source output to target input Features, 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 owning ItemFlowEnd, which would make validateRedefinitionDirectionConformance pass unconditionally. A clarification on this is needed.

  • Multiplicity bounds are parsed as arbitrary Expressions and 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 public symbols 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).