| name | mps-aspect-behavior |
| description | Use when defining or editing MPS `ConceptBehavior` — per-concept methods (non-virtual / virtual / abstract / static / virtual static), constructors, virtual dispatch (MRO), super and interface-default calls (`super<Interface>.method`), overriding methods from `lang.core.behavior` interfaces such as `ScopeProvider.getScope` / `INamedConcept.getName` / `BaseConcept.getPresentation`, calling sibling methods (`LocalBehaviorMethodCall`) and behavior methods from other aspects via `node.method(...)`. Reach for this skill whenever the task involves authoring or modifying `<lang>/languageModels/behavior.mps`. |
| type | reference |
MPS Behavior Aspect
The behavior aspect attaches methods and a constructor to a concept, much like adding methods to a Java class. Bodies are written in BaseLanguage + smodel and are callable from any other aspect (editor, constraints, typesystem, generator, intentions, plugin) via node.methodName(...). Lives in <lang>/languageModels/behavior.mps, language jetbrains.mps.lang.behavior.
Critical Directives
- One
ConceptBehavior root per concept. The constructor child is mandatory (an empty body is fine).
- Constructors run before the node is attached to the model.
parent, ancestors, and all children/descendants evaluate to null. Constructors can only set own property/reference defaults and add mandatory children — use a NodeFactory (actions aspect) for anything that needs the parent/model context.
- Quotations (
<C()>) bypass behavior constructors. Use new node<C>() or add new initialized C when you need the constructor to fire.
- Constructors are not inherited — each concept has at most one, and they do not chain (no
super()). MPS runs each ancestor's constructor in ancestor-first order independently.
- An overriding method must match the signature of the supertype/interface method exactly. When implementing an interface method (e.g.
ScopeProvider.getScope), set overriddenMethod on the new method to the interface declaration — MPS uses it for dispatch and signature validation.
- Behavior calls on a
null node do not NPE — MPS returns null for reference/String returns and the default for primitives. Still handle the null return for reference types.
- Hoist shared methods to an abstract super-concept rather than duplicating per-sub-concept
ConceptBehavior roots — virtual dispatch covers all non-abstract sub-concepts. (See references/inheritance-and-dispatch.md.)
- Methods are non-virtual by default: all modifier booleans (
isVirtual, isAbstract, isStatic, isFinal) default to false. Only virtual methods can be overridden; mark a method virtual up front if sub-concepts may ever need to specialise it.
- When the same piece of node-handling logic repeats across several places or several aspects of one concept (editor, constraints, typesystem, generator, intentions, textgen), extract it into a behavior method and call it everywhere via
node.m(...) — this de-duplication is the behavior aspect's primary purpose. Pick the modifier with the table below.
- Edit behavior models through MPS MCP tools (
mps_mcp_insert_root_node_from_json, mps_mcp_update_node, mps_mcp_parse_java_and_insert). Do not hand-edit .mps files.
- For MPS-typed return types (
sequence<node<X>>, list<node<X>>), mps_mcp_parse_java_and_insert produces Java List<SNode> — replace returnType afterwards with the correct MPS blueprint (see mps-model-manipulation/references/variable-declarations.md).
- After edits run
mps_mcp_check_root_node_problems and rebuild the language.
Common-Path Workflow
- Create a
behavior model if missing (mps_mcp_create_model with modelName: "<lang>.behavior" — aspect ID behavior, case-sensitive, no @ suffix; see aspect-model-stereotypes.md). Use languages: jetbrains.mps.lang.behavior, plus any languages referenced in bodies (smodel, collections, closures, baseLanguage).
- Add a
ConceptBehavior root for the target concept; set concept ref. The minimal blueprint (with the mandatory empty constructor) is in references/json-blueprints.md.
- Add
ConceptConstructorDeclaration (at most one) and/or ConceptMethodDeclaration children.
- Write bodies. For non-trivial logic prefer
mps_mcp_parse_java_and_insert with featureKind: "STATEMENTS" or "METHOD", then fix MPS-typed return/parameter types.
- Validate with
mps_mcp_check_root_node_problems, rebuild the language.
Method Modifier Quick Reference
| Modifier | Call shape | Dispatch / inheritance | Notes |
|---|
| (none) — non-virtual (default) | node.m(...) | Inherited by sub-concepts but statically bound — cannot be overridden | A same-named method in a sub-concept's behavior shadows it (hazard) instead of overriding. Most Classifier_Behavior utilities in baseLanguage are non-virtual. |
virtual | node.m(...) | Dispatched at runtime by the node's actual concept; overridable in sub-concept behaviors | Set isVirtual: true explicitly. E.g. Expression.isLValue and every method of Type_Behavior in baseLanguage. |
abstract | node.m(...) (virtually dispatched) | No body; every non-abstract sub-concept must provide an implementation | Implies virtual — set both isAbstract and isVirtual. Declare on abstract/interface concepts (e.g. Classifier.findAncestor, IMemberContainer.getMembers in baseLanguage). |
final | node.m(...) | A virtual method that cannot be overridden further down | Rarely needed — a plain non-virtual method is already non-overridable. |
static | ConceptName.m(...) (qualified by concept name) | Belongs to the concept; no this; no dispatch | Concept-wide utilities, e.g. Classifier.getContextClassifier in baseLanguage. Called as LocalBehaviorMethodCall when unqualified inside the same ConceptBehavior. |
virtual static | conceptValue.m(...) on a concept<X> expression | Dispatched by the runtime concept value; overridable in sub-concept behaviors (link overriddenMethod) | Per-concept (not per-node) polymorphism — e.g. Expression.getPrecedenceLevel, Type.isValueType in baseLanguage, overridden across the smodel/collections behaviors. |
Choosing a modifier for an extracted method
- Invoked on a node (needs
this, properties, children)? → instance method: virtual if sub-concepts must be able to override it (or the logic varies by concept), non-virtual for a fixed helper.
- Invoked on a concept (no node instance at hand, concept-wide utility, or nodes passed as parameters)? →
static; make it virtual static when the result must vary per concept (dispatch on a concept<X> value).
- Shared by several sub-concepts? → declare it once on their common (abstract) super-concept or interface; use
abstract (+ virtual) when only sub-concepts can supply the body.
Related Skills
mps-aspect-actions — NodeFactory is the right place for initialization that needs parent/model context (constructors run before the node is attached).
mps-aspect-intentions, mps-aspect-constraints, mps-aspect-generator, mps-aspect-typesystem — call behavior methods via node.m(...); behavior is one of the most-used aspects from these.
mps-model-manipulation — full BaseLanguage / smodel / collections reference. Covers the LinkList_AddNewChildOperation family and the List<SNode> → sequence<node<X>> return-type fix.
mps-aspect-constraints — ScopeProvider.getScope is most often overridden in a behavior; constraints describe where the reference lives.
mps-aspect-structure-concepts — when introducing the concept that the behavior attaches to.
mps-language-inheritance — to design the abstract-super-concept refactors discussed in references/inheritance-and-dispatch.md.
mps-quotations — quotations bypass behavior constructors; document the workaround in the generator.
Reference Index
- Open
references/method-declarations.md when authoring a ConceptMethodDeclaration — modifiers, parameters, overriddenMethod, BaseConcept overrides (getPresentation / getSideIcon), implicit-return rule, and the MPS-typed return-type Java-parser caveat.
- Open
references/local-and-super-calls.md when calling sibling methods of the same ConceptBehavior (LocalBehaviorMethodCall), distinguishing unqualified m(args) from this.m(args) (which generates DotExpression { ThisNodeExpression, Node_ConceptMethodCall }), or calling super<Interface>.method(...) — includes the verbatim Stateful and Kaja CommentLine_Behavior examples and the Stateful_Behavior.getScope super-call JSON.
- Open
references/inheritance-and-dispatch.md when designing or debugging virtual dispatch — the MRO algorithm (own → extended super → interfaces in definition order), how a method on an abstract super-concept covers every concrete sub-concept (Stateful pattern), and when to hoist shared behavior up vs. duplicate per concept.
- Open
references/constructors.md when writing a ConceptConstructorDeclaration — what runs and what does not run a constructor, the "node not yet attached" constraint, scalar-default and seed-mandatory-child examples (ChemMastery Compound_Behavior and ChemEquation_Behavior) with full JSON blueprints, and the surface→FQN mapping for the surface syntax involved.
- Open
references/calling-from-other-aspects.md when calling a behavior method from generator templates, typesystem rules, constraints, intentions, or hand-written Java — the descriptor invocation shape, the _idXXXXX stable suffix, and the null-safety behavior of node.method().
- Open
references/json-blueprints.md when inserting a behavior root or a method via MCP — minimal ConceptBehavior with the mandatory empty constructor, the ConceptMethodDeclaration skeleton, and the validated concept ref.
- Open
references/lang-core-behavior-overrides.md for the validated node refs to set as overriddenMethod when implementing common lang.core.behavior methods (getTextualRepresentation, , ), plus the interface node ref used in calls.