In-Memory Model Revalidation
Diagnostics from load_model reflect the model as it was loaded. Any structural
mutation — extracting elements, retyping features, flipping a usage between composite
and referential — makes them stale, and you must revalidate to see the resulting errors.
The pattern shown here reschedules the modified documents through a Pipeline built from the model’s existing static index and standard library, so
sema and validation run again on the in-memory model. Nothing is serialized and
reparsed, and the documents keep their original URLs and dependencies.
The modified documents are passed to schedule as
invalidated rather than as documents. The pipeline resets their semantic state
and sends them through sema and validation again, so neither an explicit
sema_reset nor the force_revalidation flag is needed.
Warning
A document listed in documents is skipped for every stage its build_state
has already passed. Passing an already-built document there — even alongside
invalidated — revalidates nothing and silently reports no diagnostics.
In this example, the model declares ref part engineSpec : Engine inside an
attribute. The script flips that usage from referential to composite. The
attribute-usage-features validation rule requires features of an attribute to be
referential, so revalidation reports a validation error. Reverting the flip and
revalidating again clears it, showing that the pipeline re-derives the diagnostics each
run rather than accumulating them.
Concepts Used
make_pipelineconstructs a parsing and analysis pipeline; reusing the model’sStaticIndexandStdlibpreserves dependency resolutionPipeline.scheduleaccepts aninvalidatedsequence of documents whose semantic state is reset before they are rebuiltUsage.is_referencetoggles a usage between composite (owning) and referential (non-owning)Diagnostic.severityfilters diagnostics by severity
Example Model
package VehicleSystem {
private import ScalarValues::*;
part def Engine {
attribute power : Integer;
}
part def Vehicle {
// ref makes `engineSpec` a non-owning reference, which is required
// for usages owned by an attribute. See the `attribute-usage-features`
// rule.
attribute spec {
ref part engineSpec : Engine;
}
}
}
Example Script
import pathlib
import syside
EXAMPLE_DIR = pathlib.Path(__file__).parent
MODEL_FILE_PATH = EXAMPLE_DIR / "example_model.sysml"
def find_child[T: syside.Element](
parent: syside.Namespace, kind: type[T], name: str
) -> T:
"""Return the direct child of ``parent`` of type ``kind`` named ``name``."""
return next(
e
for e in parent.children.elements
if isinstance(e, kind) and e.declared_name == name
)
def engine_spec(doc: syside.Document) -> syside.PartUsage:
"""Walk down to ``engineSpec``, nested inside the ``spec`` attribute."""
package = find_child(doc.root_node, syside.Package, "VehicleSystem")
vehicle = find_child(package, syside.PartDefinition, "Vehicle")
spec = find_child(vehicle, syside.AttributeUsage, "spec")
return find_child(spec, syside.PartUsage, "engineSpec")
def revalidate(model: syside.Model) -> list[syside.Diagnostic]:
"""Re-run sema and validation over the user documents of ``model``."""
# Reuse the model's own index and stdlib so name resolution and semantic
# constraints still see the standard library and every other document.
pipeline = syside.make_pipeline(
syside.PipelineOptions(static_index=model.index, lib=model.lib)
)
# Modified documents are passed as `invalidated`, not as `documents`: the
# pipeline resets their semantic state and sends them through sema and
# validation again. A document listed in `documents` is skipped for every
# stage its `build_state` has already passed, so passing an already-built
# document there — even alongside `invalidated` — revalidates nothing.
schedule = pipeline.schedule(
[],
syside.ScheduleOptions(
validation_timing=syside.ValidationTiming.Manual
),
invalidated=model.user_docs,
)
result = syside.get_default_executor().run(schedule)
if not result:
result.rethrow_exception()
return [
diag
for doc_diags in result.diagnostics
for stage in (doc_diags.sema, doc_diags.validation)
for diag in stage
if diag.severity == syside.DiagnosticSeverity.Error
]
def main() -> None:
(model, diagnostics) = syside.load_model([MODEL_FILE_PATH])
print(f"Errors as loaded: {len(list(diagnostics.errors))}")
with model.user_docs[0].lock() as doc:
# Flip `engineSpec` from referential to composite. The
# `attribute-usage-features` rule requires features of an attribute to
# be referential, so this makes the model invalid.
engine_spec(doc).is_reference = False
print("Modified: engineSpec switched from referential to composite.")
# Diagnostics from `load_model` describe the model as it was loaded, so they
# still report no errors. Only a fresh pipeline run sees the modification.
errors = revalidate(model)
print(f"Errors after revalidation: {len(errors)}")
for diag in errors:
print(f" - {diag.code}: {diag.message}")
# Revalidation is repeatable: undoing the modification clears the error.
with model.user_docs[0].lock() as doc:
engine_spec(doc).is_reference = True
print("Reverted: engineSpec switched back to referential.")
print(f"Errors after revalidation: {len(revalidate(model))}")
if __name__ == "__main__":
main()
Output
Errors as loaded: 0
Modified: engineSpec switched from referential to composite.
Errors after revalidation: 1
- attribute-usage-features: Features of an attribute usage must be referential
Reverted: engineSpec switched back to referential.
Errors after revalidation: 0
Download
Download this example here.