Cookbook - Type System
Inference rules
This cookbook should give you quick answers and guidelines when designing types for your languages. For in-depth description of the typesystem please refer to the Typesystem section of the user guide.
Equality
Use type equation when the type of a node should always be a particular concrete type. Use the typeof command to declare that the type of the desired node should equal to a particular type.
Note quotation is used to refer to a type. <string> is equivalent to typing new node<StringType>(). The type of an element is equal to the type of some other element. For example, the to express that parentheses preserve the type of the wrapped element, the ParenthesizedExpression concept
declares:
Inequality
When the types should be sub-types or super-types of other types, use the infer typeof command. See the ternary operator as an example:
The ForEachStatement concept illustrates how to solve quite an involved scenario. The type of the loop variable must be equal to the type of elements in the iterated collection, while the type of the collection must be a sub-type of either a sequence or an array of elements of the elementType type.
Notice, we use var elementType to declare a variable, which we then use to tie together the type of the collection elements and the type of the loop variable. Also, %(...)% demarcates so called anti-quotation, which allows you to provide values from your local context into the AST you are manipulating or retrieve them back.
Replacement rules
Replacement rules indicate to the type system the possibility to replace one type with another. For example, NullType is a subtype of all types (except for primitive types) and so the type system can simply remove the inequation between NullType and BaseConcept.
Replacement rules are also handy to declare covariance and contravariance. For example, covariance for sequences is declared in MPS as follows:
The original rule claiming that the left collection is a subtype of the right collection gets replaced with a rule ensuring that the type of elements in the left collection is a subtype of the type of elements in the right collection.
Subtyping rules
Subtyping rules allow you to specify where the particular type belongs in the type hierarchy. The rule returns a collection of types, which it identifies as its direct super-types. The following rule, for example, declares that Long variables can be cast to Float.
Here MPS declares, that LinkedList is a subtype of either a List, a Deque or a Stack:
Comparison rules
When two types should be interchangeable, use comparison rules to define that. For example, the following rule makes NullType comparable with any type, except for primitive ones:
Similarly, the MapType from BaseLanguage and the Map interface from Java (here refered to through the ClassifierType concept inside a pattern) should be comparable:
Substitute Type rules
These instruct the type-system to replace nodes representing a type with defined substitutes.
For example, one might decide to use different types for different program configurations, such as using int
or long
depending on whether the task requires using one type or another. This is different from simply using the generator to produce the correct "implementation" type, as the substitution is done at the time the typechecking is performed, so possible errors can be caught early.
In its simplest form the type substitution can be used by creating an instance of Substitute Type Rule
in the typesystem model.
substitute type rule substituteType_MyType {
applicable for concept = MyType as mt
substitute {
if (mt.isConditionSatisfied()) {
return new node<IntegerType>;
}
null;
}
}
The Substitute Type Rule
is applicable to nodes that represent types. Whenever a new type is introduced by the typechecker, it searches for applicable substitution rules and executes them. The rule must either return an instance of `node<>` as the substitution, or null value, in which case the original node is used to represent the type (the default behaviour).
One other possibility to overrides types used by the typechecker comes with the use of node attributes. If there is a node attribute contained by the original type node, the typechecker tries to find a Substitute Type Rule applicable to the attribute first. This way one can override the type nodes even for languages, which implementation is sealed.
substitute type rule substituteType_SubstituteAnnotation {
applicable for concept = SubstituteAnnotation as substituteAnnotation
substitute {
if (substituteAnnotation.condition.isSatisfied(attributedNode)) {
return substituteAnnotation.substitute;
}
null;
}
}
The rule above is defined for the attribute node, and it's the attribute node that is passed to the rule as the explicit parameter. The rule can check whether the condition for substituting the type node is satisfied, and it can also access the attributed node representing original type via attributedNode
expression.
Checking and Quick-fixes
Checking rules become part of the MPS code analysis process and will report found issues to the user interactively in the editor. For example, this is a check for superfluous type casts:
Now you can define a quick-fix that will pop-up to the user whenever the problem above is reported. The user can then quickly invoke the quick-fix to correct the reported issue.
The hook the quick-fix to the reported error, you need to specify the quick-fix as intention linked with info message(optional):
Additionally, you can pass parameters to the quick-fix and mark it with apply immediately, in which case the quick-fix will be applied automatically as soon as the error is discovered in the editor.
When-concrete, overloaded operations
When-concrete blocks allow you to perform type checks once the type a node has been calculated. In the example below we are checking, that the calculated type of an operation matches the type suggested by the operation type command based on the operator overriding rules: