Feature/application condition blocks - #40
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces explicit PAC/NAC blocks across the language, runtime, optimizer, documentation, and graphical editor.
Changes:
- Adds named
forbid/requireblocks with scopedwhereclauses. - Updates runtime planning, AST serialization, validation, and optimizer generation.
- Adds diagram actions for creating, moving, and rendering condition members.
Reviewed changes
Copilot reviewed 72 out of 72 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
website/samples/language-tour/match.mt |
Updates match example |
website/samples/language-tour/application-conditions.mt |
Adds condition-block sample |
website/docs/plugins/model-transformation.md |
Documents new syntax |
platform/optimizer/src/test/kotlin/com/mdeo/optimizer/ScrumModelTransformationCorrectnessTest.kt |
Updates optimizer expectations |
platform/optimizer/src/test/kotlin/com/mdeo/optimizer/rulegen/MutationRuleGeneratorTest.kt |
Tests generated NAC blocks |
platform/optimizer/src/main/kotlin/com/mdeo/optimizer/rulegen/MutationAstBuilder.kt |
Generates explicit NAC blocks |
platform/model-transformation/src/test/resources/applicationConditionTypedAst.json |
Adds AST fixture |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlanOrderingTest.kt |
Updates planner tests |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/plan/CostBasedMatchPlanOrderingTest.kt |
Updates planner fixtures |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/NacInjectiveConstraintOmissionTest.kt |
Tests block injectivity |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/IslandGrouperTest.kt |
Removes obsolete tests |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/InjectiveMatchTest.kt |
Migrates forbid tests |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTraversalUtilsTest.kt |
Renames traversal tests |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTestHelpers.kt |
Adds condition test builders |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionAnchorSelectionPerformanceTest.kt |
Updates performance coverage |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlockTest.kt |
Tests block planning |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlockSemanticsTest.kt |
Tests block semantics |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/EdgeCaseBugHuntingTest.kt |
Migrates edge-case tests |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternTest.kt |
Tests condition deserialization |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/PatternElementsTest.kt |
Tests condition AST |
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/ApplicationConditionWireFormatTest.kt |
Verifies wire format |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlanDependencies.kt |
Excludes condition nodes |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlan.kt |
Adds condition plan steps |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/PatternCategories.kt |
Categorizes explicit conditions |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/MatchTraversalBuilder.kt |
Builds condition traversals |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/MatchExecutor.kt |
Compiles condition bindings |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/IslandGrouper.kt |
Removes legacy grouping |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/GraphModificationApplier.kt |
Updates utility reference |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTraversalUtils.kt |
Renames condition traversal utilities |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlock.kt |
Models runtime condition blocks |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/compiler/VariableScope.kt |
Supports temporary unbinding |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternElementSerializer.kt |
Deserializes conditions |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternElement.kt |
Adds condition element |
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/PatternElements.kt |
Adds condition AST model |
app/packages/service-model-transformation/src/handler/modelTransformationTypedAstConverter.ts |
Converts condition blocks |
app/packages/protocol-model-transformation/src/types.ts |
Clarifies effective modifiers |
app/packages/protocol-model-transformation/src/operations/movePatternElementToConditionOperation.ts |
Adds move operation |
app/packages/protocol-model-transformation/src/operations/addWhereClauseOperation.ts |
Targets block clauses |
app/packages/protocol-model-transformation/src/index.ts |
Exports move protocol |
app/packages/language-model-transformation/src/validation/modelTransformationValidator.ts |
Validates condition blocks |
app/packages/language-model-transformation/src/plugin/typedAst.ts |
Extends typed AST |
app/packages/language-model-transformation/src/index.ts |
Exports condition features |
app/packages/language-model-transformation/src/grammar/modelTransformationTypes.ts |
Defines condition grammar types |
app/packages/language-model-transformation/src/grammar/modelTransformationRules.ts |
Parses block syntax |
app/packages/language-model-transformation/src/features/type-system/modelTransformationTypirScopeProvider.ts |
Creates block scopes |
app/packages/language-model-transformation/src/features/type-system/modelTransformationPartialTypeSystem.ts |
Restricts block-local identifiers |
app/packages/language-model-transformation/src/features/type-system/modelTransformationApplicationConditionScope.ts |
Implements condition scope |
app/packages/language-model-transformation/src/features/modelTransformationSerializers.ts |
Prints condition blocks |
app/packages/language-model-transformation/src/features/modelTransformationScopeComputation.ts |
Scopes block declarations |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationPatternUtils.ts |
Adds condition utilities |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationModelIdProvider.ts |
Qualifies clause IDs |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationMetadataManager.ts |
Includes block members |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationLabelParseUtils.ts |
Strips condition stereotypes |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationLabelEditValidator.ts |
Validates block labels |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationGModelFactory.ts |
Renders condition members |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationDiagramModule.ts |
Registers move handler |
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationCreateEdgeSchemaResolver.ts |
Restricts cross-block links |
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationReconnectEdgeOperationHandler.ts |
Validates reconnection scope |
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationPasteOperationHandler.ts |
Excludes block-local targets |
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationDeleteElementOperationHandler.ts |
Cascades block deletions |
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationApplyLabelEditOperationHandler.ts |
Removes legacy modifiers |
app/packages/language-model-transformation/src/features/diagram-server/handler/createPatternLinkOperationHandler.ts |
Creates links inside blocks |
app/packages/language-model-transformation/src/features/diagram-server/handler/createPatternInstanceOperationHandler.ts |
Creates initial blocks |
app/packages/language-model-transformation/src/features/diagram-server/handler/changePatternElementModifierOperationHandler.ts |
Limits element modifiers |
app/packages/language-model-transformation/src/features/diagram-server/handler/addWhereClauseOperationHandler.ts |
Adds block-local clauses |
app/packages/editor-model-transformation/src/views/whereClauseLabelView.ts |
Sends condition index |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
szschaler
left a comment
There was a problem hiding this comment.
As far as I can tell, this is OK, though see my suggestions below.
My main comment is that this is surprisingly complicated for what it actually does. This suggests that there is a lot of work that we should do to refactor the codebase and make it easier to make this kind of relatively straightforward change. I would like us to be able to use plain grammars and generate more from it, in the standard Langium style. Ideally, we would also generate the full blended editor from a single source of truth -- we seem to be repeating a lot of logic for both editing modalities at the moment -- but that may need a bit more research work (not sure if Malvina Latifaj's PhD work offers something here already?). Using BoFan's Langium--GLSP integration would have helped with some of the scoping stuff, I think.
Maybe we can set up a discussion about architecture for September?
| .isNewLabel(true) | ||
| .newLabelOperationKind("add-where-clause") | ||
| .newLabelOperationKind( | ||
| conditionIndex != undefined ? `add-where-clause:${conditionIndex}` : "add-where-clause" |
There was a problem hiding this comment.
Should this also use the constant introduced in whereClauseLabelView.ts?
There was a problem hiding this comment.
More generally, should such constants be centralised somewhere?
| * {@link MovePatternElementToConditionOperationHandler}. Elements declared inside a block | ||
| * therefore have no modifier to change, and the action is not offered for them. |
There was a problem hiding this comment.
Does that mean, we do not offer nested application conditions as in standard graph transformation approaches?
There was a problem hiding this comment.
currently not, we can introduce this, but for me this would be a new PR
| private shareAGraph(source: AstNode, target: AstNode): boolean { | ||
| const reflection = this.modelState.languageServices.shared.AstReflection; | ||
| const sourcePattern = findContainingPattern(source, reflection); | ||
| if (sourcePattern == undefined || sourcePattern !== findContainingPattern(target, reflection)) { | ||
| return false; | ||
| } | ||
|
|
||
| const sourceCondition = findContainingCondition(source, reflection); | ||
| const targetCondition = findContainingCondition(target, reflection); | ||
| return sourceCondition == undefined || targetCondition == undefined || sourceCondition === targetCondition; | ||
| } | ||
|
|
There was a problem hiding this comment.
This seems like a roundabout way of doing this. Could you not simply use standard hierarchical scoping? You have the hierarchy of patterns already and just need to establish the standard rule that scopes can include stuff directly declared in containing patterns, but nothing else... That should be implemented as a scope rule in one place and then used everywhere else...
There was a problem hiding this comment.
the GModel uses a flat structure currently, as this is what the unified graph displays (and a hierarchical structure would introduce complications, in particular regarding the positioning logic)
but maybe we can improve on this in the future
| * @returns The expression string, or undefined if the prefix is missing | ||
| */ | ||
| export function extractWhereClauseExpression(label: string): string | undefined { | ||
| const prefix = "where "; |
There was a problem hiding this comment.
See my comment about globally defined constants earlier: here we are now using a different mechanism for identifying these clauses, which may well result in a different set of nodes from that used elsewhere...
| * @param reflection The AST reflection instance for type checks. | ||
| * @returns The elements in declaration order, block members after the main pattern. | ||
| */ | ||
| export function flattenPatternElements( |
There was a problem hiding this comment.
If I understand correctly, this doesn't yet support nested applications conditions (Henshin uses /-separated names for these), correct?
There was a problem hiding this comment.
yes, we can implement this in a future PR
| } | ||
|
|
||
| /** | ||
| * The level of the enclosing scope: a condition block adds names, not a run-time scope. |
There was a problem hiding this comment.
Fixed with 24c323e condition blocks now get a scope of their own instead of that special case
| accept({ | ||
| languageNode: node, | ||
| message: | ||
| `Identifier '${node.name}' cannot be used in expressions outside its block: it is ` + | ||
| `declared inside the '${kind}' block and is not bound by the match.`, | ||
| severity: "error" | ||
| }); |
There was a problem hiding this comment.
Shouldn't this be handled by scoping already?
There was a problem hiding this comment.
Fixed with bb30306 it did, unreachable check removed
The adapter for generated transformations dropped condition blocks, so the ones the mutation rule generator emits were missing from the diagram and its metadata. The move handler could produce source its own validator rejects: it left emptied blocks behind, carried modifiers and assignments into blocks, and moved elements out from under expressions reading them. The dangling-name scan now runs in both directions and walks identifier expressions instead of clause text.
A block reported the level of its parent, so the engine had to bind its nodes in the match's scope and unbind them once the traversal was built. It now reports its own level and the engine opens a scope per block, which nested blocks get for free.
The index was encoded into the label's operation kind and taken apart again in the view. It now travels as an operation argument, and names the placeholder label, which is the only thing the edit validator is given.
Operation kinds, placeholder label kinds, the toolbox enums, the create-edge contexts, the custom icon names and the layout metadata keys each existed twice, once per side. They now come from the protocol packages both sides already depend on.
A generated name like forbid2 says nothing the source did not already say. New blocks are now written unnamed, and every unnamed block is shown under a number that skips the names its named siblings occupy.
Forbid and require sit next to create and delete again, each listing the blocks of its kind. Choosing create or delete for a block member moves it into the match pattern and writes the modifier there in one edit, a new block is only offered where it would say something new, and every menu measures both directions before it opens.
A match node is drawn as large as the pattern it holds, which is now the floor it can be dragged from rather than the only size it has. The change bounds tool asks an element for a minimum size of its own, and the frame of the match node stops swallowing the pointer events of its resize handles.
|
I also added some UI improvements, made the names of application conditions optional (numbered by default if misssing), and added manual resize support for match patterns |
Puts PACs/NACs into separate blocks
Basically as discussed in #10
Also adds support for where conditions in these blocks
example: