MPS Constraints Aspect
SkillSearchUse when defining or editing MPS language constraints — property validators / setters / getters, referent search scopes (imperative or inherited via `ScopeProvider.getScope`), `referentSetHandler` side effects, default-scope blocks, `canBeChild` / `canBeParent` / `canBeAncestor` / `canBeRoot` placement rules, `defaultConcreteConcept` for abstract concepts, `set <read-only>` and `{name}` aliasing, and scope helpers (`SimpleRoleScope`, `ListScope`, `CompositeScope`, `HidingByNameScope`). Reach for this skill whenever the task involves authoring or modifying `<lang>/languageModels/constraints.mps`.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the MPS Constraints Aspect skill
What this skill tells your AI
The instructions your AI receives, as published by jetbrains/mps in .agents/skills/mps-aspect-constraints/SKILL.md and read by ahel’s review.
The constraints aspect controls runtime rules about nodes: what values properties may hold, how a property is stored when set, what nodes a reference may point to, and where a node of a concept is allowed to appear in the AST. It lives in the language's constraints model (<lang>/languageModels/constraints.mps) and uses the language jetbrains.mps.lang.constraints.
Constraints are enforced at edit time and also consulted by the typesystem/editor. They are not the place for structural cardinalities (that belongs in structure) or for types (that belongs in typesystem).
Critical Directives
- One
ConceptConstraintsroot per concept. Set itsconceptreference; everything else is child blocks. - Pick the right body for the job:
- Validator (
propertyValidator) — pure acceptance check; returnsboolean; never mutates state. - Setter (
propertySetter) — normalises and must explicitly assignnode.<prop> = propertyValue(a body that forgets this silently drops the value). - Getter (
propertyGetter) — only when the displayed value differs from the stored value (derived URL, aliased name). set <read-only>means no setter at all — direct assignment to the property is rejected by MPS. An empty-body setter is almost always a bug.
- Validator (
canBeChild/canBeParent/canBeAncestorare inherited by sub-concepts; per-property and per-reference constraints are not — duplicate theConceptConstraintsroot or hoist the rule up.- Pick the right scope shape:
- Inherited via
InheritedNodeScopeFactory+ScopeProvider.getScope(behavior) — preferred whenever the scope naturally belongs to an ancestor (block, declaration, program, module). Composes cleanly along the containment chain withparent scope. - Imperative
ConstraintFunction_ReferentSearchScope_Scope— only when the scope is tiny, highly local, and has no natural ancestor owner.
- Inherited via
referentSetHandlerperforms side effects (auto-rename referrer, copy fields,keeps original reference). Never overload the scope to do this.defaultConcreteConceptonConceptConstraintschooses which concrete subconcept replaces an abstract pick — it is a single reference, not a child block.- Surface forms (
come from,parent scope,kind.isSubConceptOf(...),link/C : role/,concept/C/) are not BaseLanguage. Each is a dedicatedjetbrains.mps.lang.scopes/jetbrains.mps.lang.smodelconcept — construct those concepts, not strings (seereferences/scope-fqn-reference.md). Concept_IsSubConceptOfOperationmatches a concept and its sub-concepts;Concept_IsExactlyOperationexcludes sub-concepts. UseisExactlyfor guards that must not fire on specialisations.- Edit constraint models through MPS MCP (
mps_mcp_insert_root_node_from_json,mps_mcp_update_node,mps_mcp_parse_java_and_insert). Do not hand-edit.mpsfiles. Validate withmps_mcp_check_root_node_problemsand rebuild the language; if the new constraint is invisible at runtime, runmps_mcp_reload_all.
Common-Path Workflow
- Ensure the language has a
constraintsmodel; create it withmps_mcp_create_model(modelName: "<lang>.constraints"— aspect IDconstraints, case-sensitive, no@suffix; see aspect-model-stereotypes.md) if absent. - Add
jetbrains.mps.lang.constraintsto the model's used languages. For scope bodies also addjetbrains.mps.lang.smodel,jetbrains.mps.lang.scopes,jetbrains.mps.lang.behavior,jetbrains.mps.baseLanguage; import modelsjetbrains.mps.scopeandjetbrains.mps.lang.core.behavior.- Module-level dependency for scope classes: the language module must have a
Defaultdependency on solutionjetbrains.mps.kernel(which ships modeljetbrains.mps.scope).mps_mcp_model_dependencyadds it automatically when importingjetbrains.mps.scope;mps_mcp_create_module type=languagedoes not. Seereferences/scope-fqn-reference.md→ "Required module-level dependency".
- Module-level dependency for scope classes: the language module must have a
- For each concept to constrain, create one
ConceptConstraintsroot (mps_mcp_create_root_node); setconceptref. Minimal blueprint and validated FQN/c-ref inreferences/concept-roots.md. - Add
NodePropertyConstraint/NodeReferentConstraint/canBe*children as needed. For inherited scopes, also add agetScopemethod to the owning ancestor'sConceptBehavior(seemps-aspect-behavior). - Write bodies in BaseLanguage + smodel — follow the
mps-model-manipulationskill for smodel / collections / closures syntax. Prefermps_mcp_parse_java_and_insertfor the BaseLanguage skeleton, then drop in scope / constraint-specific concepts via MCP. - Validate with
mps_mcp_check_root_node_problemsand rebuild the language.
Related Skills
mps-aspect-behavior—ScopeProvider.getScopelives in the behavior aspect; this is the partner of everyInheritedNodeScopeFactoryconstraint. Behavior is also where most validator/getter delegation lands.mps-aspect-structure-concepts— structural cardinalities belong instructure, not here. AddINamedConcept/IResolveInfo/ScopeProviderinterfaces on the concept declaration before writing the matching constraint or scope.mps-aspect-editor-menus-and-keymaps— completion menus consume the scope built here; substitute / transformation menus are the place for actions that go beyond picking an existing target.mps-aspect-actions— when a constructor-style initialization is needed, useNodeFactory(actions aspect), not a constraint.mps-aspect-intentions— when a suggested fix belongs on a node, write an intention; constraints only accept or reject.mps-model-manipulation— full BaseLanguage / smodel / collections / closures reference for constraint and scope bodies. Indispensable forancestor<concept = X, +>,selectMany,siblings.ofConcept<X>.all(...), anonymous-class scopes, and the smodel operation FQNs.mps-aspect-typesystem— type checks belong in typesystem; constraints are about value/scope/placement.mps-aspect-textgenandmps-aspect-constraints-co-located utility classes — seereferences/concept-roots.mdfor the BaseLanguageClassConceptco-location pattern (used byXmlNameUtil).
Reference Index
- Open
references/concept-roots.mdwhen creating theConceptConstraintsroot, settingdefaultConcreteConceptto a concrete subconcept of an abstract concept, looking up the validated concept c-ref, or co-locating a BaseLanguage helperClassConceptnext to constraint roots. - Open
references/property-constraints.mdwhen writingNodePropertyConstraintbodies — choosing validator vs. setter vs. getter, the{name}aliasing pattern withset <read-only>, derived-property getters (e.g.Element.details_url), sibling-uniqueness validators (State.isInitial), and full JSON blueprints for validator and getter bodies. - Open
references/referent-constraints.mdwhen writingNodeReferentConstraint— both scope styles (imperativeConstraintFunction_ReferentSearchScope_Scopeand ancestor-suppliedInheritedNodeScopeFactory), the verbatim Calculator / StateChart / Kaja examples (SimpleRoleScope.forNamedElements,ListScopewith anonymousgetName,CompositeScope+addScope,model.rootsIncludingImported), the meta-level scope offering structure declarations (conceptNode.getAggregationLinkDeclarations()forLinkDeclarationreferents),referentSetHandlerpatterns (auto-rename, multi-field copy,keeps original reference), and concept-levelDefault Scope. - Open
references/scope-helpers.mdwhen picking amongEmptyScope,ListScope,SimpleRoleScope,CompositeScope,FilteringScope,DelegatingScope,ModelsScope,ModelPlusImportedScope,Scopes.forConcepts,HidingByNameScope, orListScope.forResolvableElements; or when caching withfor model [...],visible roots [C],visible nodes [C]. Includes the verbatim StateChartStateful_Behavior.getScope(ListScope+ anonymousgetName+HidingByNameScopeoverparent scope) idiom. - Open
references/scope-fqn-reference.mdwhen constructing scope bodies via MCP — the full surface-syntax-to-FQN table (parent scope,come from,isSubConceptOfvsisExactly,RefConcept_ReferencevsConceptIdRefExpression,LinkIdRefExpression,Node_GetAncestorOperation+OperationParm_Concept,Node_GetDescendantsOperation,Node_GetModelOperation,Model_RootsIncludingImportedOperation,Node_GetParentOperation,Node_IsInstanceOfOperation), required used-languages and model imports, the validatedScopeProvider.getScopeoverriddenMethod ref, and JSON blueprints for thegetScopeskeleton,parent scopereturn, and the guarded local-scopeif-branch. - Open
references/canbe-rules.mdwhen authoringConstraintFunction_CanBeAChild/CanBeAParent/CanBeAnAncestor/CanBeARoot— each block's parameter set (notecanBeAnAncestorhas only 3 parameters; noparentNodeorlink), theparentNode.parent.isInstanceOf(...)grandparent-traversal pattern (KajaRoutineDefinitionandRequire), thenode.isInRole(link/C : role/)pattern, themodelparameter forcanBeRoot, and the minimalConstraintFunction_CanBeAChildblueprint. - Open
references/common-failures.mdwhen a setter runs but stores nothing, a completion popup is empty, the editor reports "cannot be child", a constraint does not apply to a sub-concept, or constraint edits seem invisible at runtime.
Signals
- GitHub stars
- 2k
- Forks
- 309
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
mps-aspect-constraints- Source
- github.com/jetbrains/mps