name: mps-aspect-constraints
description: Use 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.
type: reference
MPS Constraints Aspect
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),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.