MPS 2019.1ヘルプ

クックブック - 型システム

推論ルール

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 型システム 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.

rule typeof_StringLiteral { applicable for concept = StringLiteral as nodeToCheck applicable always overrides false do { typeof(nodeToCheck) :==: <string>; } }

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:

rule typeOf_ParenthesizedExpression { applicable for concept = ParenthesizedExpression as parExpr applicable always overrides false do { typeof(parExpr) :==: typeof(parExpr.expression); } }

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:

rule typeOf_TernaryOperator { applicable for concept = TernaryOperatorExpression as toe applicable always overrides false do { infer typeof(toe.condition) :<=: <boolean>; infer typeof(toe) :>=: typeof(toe.ifTrue); infer typeof(toe) :>=: typeof(toe.ifFalse); } }

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.

rule typeof_ForEachStatement { applicable for concept = ForEachStatement as forEachStatement applicable always overrides false do { node<ForEachVariable> variable = forEachStatement.variable; node<Expression> inputSequence = forEachStatement.inputSequence; if (inputSequence.isNotNull && variable.isNotNull) { var elementType; infer <join(sequence<%( elementType)%>| %( elementType)%[])> :>=: typeof(inputSequence); typeof(variable) :==: elementType; } } }

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 rule any_type_supertypeof_nulltype applicable for concept = NullType as nullType <: concept = BaseConcept as baseConcept custom condition: ()->boolean { !(baseConcept.isInstanceOf(RuntimeTypeVariable)); } rule { if (baseConcept.isInstanceOf(PrimitiveType) || baseConcept.isInstanceOf(PrimitiveTypeDescriptor)) { error "null type is not a subtype of primitive type" -> equationInfo.getNodeWithError(); } }

Replacement rules are also handy to declare covariance and contravariance. For example, covariance for sequences is declared in MPS as follows:

replacement rule sequence_subtypeOf_sequence applicable for concept = SequenceType as left <: concept = SequenceType as right custom condition: true rule { if (right.elementType.isNotNull) { infer left.elementType :<=: right.elementType; } }

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 長い variables can be cast to フロート .

subtyping rule long_extends_float { weak = false applicable for concept = LongType as longType rule { return <float>; } }

Here MPS declares, that LinkedList is a subtype of either a リスト , a デキュー or a スタック :

subtyping rule supertypesOf_linkedlist { weak = false applicable for concept = LinkedListType as llt rule { nlist<> res = new nlist<>; res.add(<list<%( llt.elementType)%>>); res.add(<deque<%( llt.elementType)%>>); res.add(<stack<%( llt.elementType)%>>); return res; } }

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:

comparison rule any_type_comparable_with_nulltype applicable for concept = BaseConcept as baseConcept , concept = NullType as nullType rule { if (baseConcept.isInstanceOf(PrimitiveType) || baseConcept.isInstanceOf(PrimitiveTypeDescriptor)) { return false; } return true; } weak = false

Similarly, the MapType from BaseLanguage and the マップ interface from Java (here refered to through the ClassifierType concept inside a pattern) should be comparable:

comparison rule map_type_comparableWith_Map applicable for concept = MapType as mapType , > Map<# KEY, # VALUE> < as classifierMapType rule { return true; } weak = true

Substitute Type rules

These instruct the type-system to replace nodes representing a type with defined substitutes.

例:タスクが1つのタイプを使用するのか別のタイプを使用するのかに応じて、int または long を使用するなど、プログラム構成ごとに異なるタイプを使用することを決定する場合があります。これは、タイプチェックの実行時に置換が行われるため、単純にジェネレータを使用して正しい「実装」タイプを生成するのとは異なるため、発生する可能性のあるエラーを早期に発見できます。

最も単純な形式では、型システムモデルで Substitute Type Rule のインスタンスを作成することによって型置換を使用できます。

substitute type rule substituteType_MyType { applicable for concept = MyType as mt substitute {   if (mt.isConditionSatisfied()) { return new node<IntegerType>; } null; } } 

Substitute Type Rule は型を表すノードに適用可能です。タイプチェッカによって新しい型が導入されるときはいつでも、適切な代入規則を検索して実行します。この規則は、置換として `node <>`のインスタンス、またはnull値を返さなければなりません。その場合、元のノードが型を表すのに使われます(デフォルトの振る舞い)。

タイプチェッカーによって使用される型をオーバーライドするもう1つの可能性は、ノード属性の使用にあります。元のタイプノードに含まれるノード属性がある場合、タイプチェッカーは最初にその属性に適用可能な代替タイプルールを見つけようとします。このようにして、言語が実装されていても型ノードを上書きすることができます。

substitute type rule substituteType_SubstituteAnnotation { applicable for concept = SubstituteAnnotation as substituteAnnotation substitute { if (substituteAnnotation.condition.isSatisfied(attributedNode)) { return substituteAnnotation.substitute; } null;  } }

上記のルールは属性ノードに対して定義されており、明示的なパラメータとしてルールに渡されるのは属性ノードです。この規則は、型ノードを置換するための条件が満たされているかどうかをチェックでき、attributedNode 式を介して元の型を表す属性付きノードにもアクセスできます。

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:

checking rule CheckExcessTypeCasts { applicable for concept = CastExpression as expr overrides false do { if (isStrongSubtype(expr.expression.type :<< expr.type)) { info "Typecast expression is superflous" -> expr ; } } }

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.

quick fix RemoveExcessTypeCast arguments: node<CastExpression> castExpr fields: << ... >> description(node)->string { "Remove Excess Typecast"; } execute(node)->void { castExpr.replace with(castExpr.expression); }

The hook the quick-fix to the reported error, you need to specify the quick-fix as intention linked with info message(optional) :

checkingRules153

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:

rule typeof_BinaryOperation { applicable for concept = BinaryOperation as operation overrides false do { when concrete (typeof(operation.leftExpression) as leftType) { when concrete (typeof(operation.rightExpression) as rightType) { node<> opType = operation type( operation , leftType , rightType ); if (opType.isNotNull) { typeof(operation) :==: opType; } else { error "operation is not applicable to these operands" -> operation; } } } } }
最終更新日: 2019年6月7日