diff options
author | 2024-06-17 16:47:33 +0200 | |
---|---|---|
committer | 2024-06-17 16:47:33 +0200 | |
commit | ac5b71917023cec36c3c5bacf779692542ff8e1a (patch) | |
tree | e14fc5d1473c4951ee32c4a82ea6d8b72e4857fd /subprojects/language | |
parent | chore(deps): bump dependencies (diff) | |
parent | chore(deps): bump dependencies (diff) | |
download | refinery-ac5b71917023cec36c3c5bacf779692542ff8e1a.tar.gz refinery-ac5b71917023cec36c3c5bacf779692542ff8e1a.tar.zst refinery-ac5b71917023cec36c3c5bacf779692542ff8e1a.zip |
Merge pull request #60 from kris7t/rules
Refinement rule support
Diffstat (limited to 'subprojects/language')
18 files changed, 488 insertions, 158 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext index 08f7a585..10e994a0 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -18,7 +18,7 @@ enum ModuleKind: | |||
18 | Statement: | 18 | Statement: |
19 | ImportStatement | Assertion | ClassDeclaration | EnumDeclaration | | 19 | ImportStatement | Assertion | ClassDeclaration | EnumDeclaration | |
20 | DatatypeDeclaration | AggregatorDeclaration | PredicateDefinition | | 20 | DatatypeDeclaration | AggregatorDeclaration | PredicateDefinition | |
21 | /* FunctionDefinition | RuleDefinition | */ | 21 | /* FunctionDefinition | */ RuleDefinition | |
22 | ScopeDeclaration | NodeDeclaration; | 22 | ScopeDeclaration | NodeDeclaration; |
23 | 23 | ||
24 | ImportStatement: | 24 | ImportStatement: |
@@ -45,7 +45,7 @@ AggregatorDeclaration: | |||
45 | "extern" "aggregator" name=Identifier "."; | 45 | "extern" "aggregator" name=Identifier "."; |
46 | 46 | ||
47 | enum ReferenceKind: | 47 | enum ReferenceKind: |
48 | REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container"; | 48 | REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container" | PARTIAL="partial"; |
49 | 49 | ||
50 | ReferenceDeclaration: | 50 | ReferenceDeclaration: |
51 | (referenceType=[Relation|NonContainmentQualifiedName] | | 51 | (referenceType=[Relation|NonContainmentQualifiedName] | |
@@ -75,35 +75,40 @@ Conjunction: | |||
75 | // | 75 | // |
76 | //Case: | 76 | //Case: |
77 | // Conjunction ({Match.condition=current} "->" value=Expr)?; | 77 | // Conjunction ({Match.condition=current} "->" value=Expr)?; |
78 | //RuleDefinition: | 78 | |
79 | // "rule" | 79 | enum RuleKind: |
80 | // name=Identifier | 80 | DECISION="decision" | PROPAGATION="propagation"; |
81 | // "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | 81 | |
82 | // (":" preconditions+=Conjunction (";" preconditions+=Conjunction)*)? | 82 | RuleDefinition: |
83 | // "==>" consequents+=Consequent (";" consequents+=Consequent)*)? | 83 | kind=RuleKind? "rule" |
84 | // "."; | 84 | name=Identifier |
85 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | ||
86 | ("<->" preconditions+=Conjunction (";" preconditions+=Conjunction)*)? | ||
87 | ("==>" consequents+=Consequent (";" consequents+=Consequent)*)? | ||
88 | "."; | ||
89 | |||
90 | enum ParameterBinding: | ||
91 | FOCUS="&" | MULTI="*"; | ||
85 | 92 | ||
86 | Parameter: | 93 | Parameter: |
87 | parameterType=[Relation|QualifiedName]? name=Identifier; | 94 | ( |
95 | (concreteness=Concreteness? modality=Modality)? | ||
96 | parameterType=[Relation|QualifiedName] | ||
97 | )? binding=ParameterBinding? name=Identifier; | ||
88 | 98 | ||
89 | //Consequent: | 99 | Consequent: |
90 | // actions+=Action ("," actions+=Action)*; | 100 | actions+=Action ("," actions+=Action)*; |
91 | // | 101 | |
92 | //Action: | 102 | Action: |
93 | // AssertionAction | DeleteAction | NewAction; | 103 | AssertionAction; |
94 | // | 104 | |
95 | //AssertionAction: | 105 | AssertionAction: |
96 | // value=ShortLogicValue? atom=Atom | | 106 | relation=[Relation|QualifiedName] |
97 | // atom=Atom (overwrite?=":=" | "<:") value=LogicValue; | 107 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")" |
98 | // | 108 | ":" value=Expr | |
99 | //DeleteAction: | 109 | value=ShortLogicConstant |
100 | // "delete" variableOrNode=[VariableOrNode|QualifiedName]; | 110 | relation=[Relation|QualifiedName] |
101 | // | 111 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")"; |
102 | //NewAction: | ||
103 | // "new" variable=NewVariable ("<:" parent=[VariableOrNode|QualifiedName])?; | ||
104 | // | ||
105 | //NewVariable: | ||
106 | // name=Identifier; | ||
107 | 112 | ||
108 | Expr: | 113 | Expr: |
109 | AssignmentExpr; | 114 | AssignmentExpr; |
@@ -159,7 +164,7 @@ RangeExpr returns Expr: | |||
159 | 164 | ||
160 | UnaryExpr returns Expr: | 165 | UnaryExpr returns Expr: |
161 | ArithmeticUnaryExpr | NegationExpr | | 166 | ArithmeticUnaryExpr | NegationExpr | |
162 | CountExpr | AggregationExpr | CastExpr; | 167 | CountExpr | AggregationExpr | ModalExpr | CastExpr; |
163 | 168 | ||
164 | enum UnaryOp: | 169 | enum UnaryOp: |
165 | PLUS="+" | MINUS="-"; | 170 | PLUS="+" | MINUS="-"; |
@@ -177,6 +182,15 @@ AggregationExpr: | |||
177 | aggregator=[AggregatorDeclaration|QualifiedName] | 182 | aggregator=[AggregatorDeclaration|QualifiedName] |
178 | "{" value=Expr "|" condition=ComparisonExpr "}"; | 183 | "{" value=Expr "|" condition=ComparisonExpr "}"; |
179 | 184 | ||
185 | enum Concreteness: | ||
186 | CANDIDATE="candidate"; | ||
187 | |||
188 | enum Modality: | ||
189 | MUST="must" | MAY="may"; | ||
190 | |||
191 | ModalExpr: | ||
192 | concreteness=Concreteness? modality=Modality body=UnaryExpr; | ||
193 | |||
180 | CastExpr returns Expr: | 194 | CastExpr returns Expr: |
181 | CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?; | 195 | CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?; |
182 | 196 | ||
@@ -281,7 +295,7 @@ Identifier: | |||
281 | 295 | ||
282 | NonContainmentIdentifier: | 296 | NonContainmentIdentifier: |
283 | ID | "atom" | "multi" | "contained" | "problem" | "module" | | 297 | ID | "atom" | "multi" | "contained" | "problem" | "module" | |
284 | "datatype" | "aggregator"; | 298 | "datatype" | "aggregator" | "decision" | "propagation"; |
285 | 299 | ||
286 | Real returns ecore::EDouble: | 300 | Real returns ecore::EDouble: |
287 | EXPONENTIAL | INT "." (INT | EXPONENTIAL); | 301 | EXPONENTIAL | INT "." (INT | EXPONENTIAL); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java index d6ece1ea..c6fca89a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java | |||
@@ -35,7 +35,7 @@ public class ProblemFormatter extends AbstractJavaFormatter { | |||
35 | surroundNewLines(doc, assertion, this::singleNewLine); | 35 | surroundNewLines(doc, assertion, this::singleNewLine); |
36 | var region = regionFor(assertion); | 36 | var region = regionFor(assertion); |
37 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__DEFAULT), this::oneSpace); | 37 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__DEFAULT), this::oneSpace); |
38 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__RELATION), this::noSpace); | 38 | doc.append(region.feature(ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION), this::noSpace); |
39 | formatParenthesizedList(region, doc); | 39 | formatParenthesizedList(region, doc); |
40 | for (var argument : assertion.getArguments()) { | 40 | for (var argument : assertion.getArguments()) { |
41 | doc.format(argument); | 41 | doc.format(argument); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java index 8fd60364..505c7787 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -154,6 +154,9 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
154 | if (ProblemUtil.isTypeLike(relation)) { | 154 | if (ProblemUtil.isTypeLike(relation)) { |
155 | builder.put(TYPE_LIKE, TYPE_LIKE_TRUE); | 155 | builder.put(TYPE_LIKE, TYPE_LIKE_TRUE); |
156 | } | 156 | } |
157 | } else if (eObject instanceof RuleDefinition) { | ||
158 | // Rule definitions and predicates live in the same namespace. | ||
159 | builder.put(SHADOWING_KEY, SHADOWING_KEY_RELATION); | ||
157 | } else if (eObject instanceof AggregatorDeclaration) { | 160 | } else if (eObject instanceof AggregatorDeclaration) { |
158 | builder.put(SHADOWING_KEY, SHADOWING_KEY_AGGREGATOR); | 161 | builder.put(SHADOWING_KEY, SHADOWING_KEY_AGGREGATOR); |
159 | } | 162 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java index d94c9a13..21f64236 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java | |||
@@ -31,11 +31,11 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { | |||
31 | public IScope getScope(EObject context, EReference reference) { | 31 | public IScope getScope(EObject context, EReference reference) { |
32 | var scope = super.getScope(context, reference); | 32 | var scope = super.getScope(context, reference); |
33 | if (reference == ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE) { | 33 | if (reference == ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE) { |
34 | return getNodesScope(context, scope); | 34 | // On the right side of a rule, assertion arguments may refer to variables. |
35 | var rule = EcoreUtil2.getContainerOfType(context, RuleDefinition.class); | ||
36 | return rule == null ? getNodesScope(context, scope) : getVariableScope(context, scope); | ||
35 | } | 37 | } |
36 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE | 38 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) { |
37 | || reference == ProblemPackage.Literals.NEW_ACTION__PARENT | ||
38 | || reference == ProblemPackage.Literals.DELETE_ACTION__VARIABLE_OR_NODE) { | ||
39 | return getVariableScope(context, scope); | 39 | return getVariableScope(context, scope); |
40 | } | 40 | } |
41 | if (reference == ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE) { | 41 | if (reference == ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE) { |
@@ -81,13 +81,6 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { | |||
81 | switch (currentContext) { | 81 | switch (currentContext) { |
82 | case ExistentialQuantifier quantifier -> variables.addAll(quantifier.getImplicitVariables()); | 82 | case ExistentialQuantifier quantifier -> variables.addAll(quantifier.getImplicitVariables()); |
83 | case Match match -> variables.addAll(match.getCondition().getImplicitVariables()); | 83 | case Match match -> variables.addAll(match.getCondition().getImplicitVariables()); |
84 | case Consequent consequent -> { | ||
85 | for (var literal : consequent.getActions()) { | ||
86 | if (literal instanceof NewAction newAction && newAction.getVariable() != null) { | ||
87 | variables.add(newAction.getVariable()); | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | default -> { | 84 | default -> { |
92 | // Nothing to add. | 85 | // Nothing to add. |
93 | } | 86 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java index 3432b2d8..225bf49d 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java | |||
@@ -12,9 +12,11 @@ import org.eclipse.xtext.serializer.sequencer.ITransientValueService.ValueTransi | |||
12 | import tools.refinery.language.model.problem.Assertion; | 12 | import tools.refinery.language.model.problem.Assertion; |
13 | import tools.refinery.language.model.problem.LogicConstant; | 13 | import tools.refinery.language.model.problem.LogicConstant; |
14 | import tools.refinery.language.model.problem.LogicValue; | 14 | import tools.refinery.language.model.problem.LogicValue; |
15 | import tools.refinery.language.model.problem.ProblemPackage; | ||
16 | import tools.refinery.language.services.ProblemGrammarAccess; | 15 | import tools.refinery.language.services.ProblemGrammarAccess; |
17 | 16 | ||
17 | import static tools.refinery.language.model.problem.ProblemPackage.Literals.ABSTRACT_ASSERTION__ARGUMENTS; | ||
18 | import static tools.refinery.language.model.problem.ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION; | ||
19 | |||
18 | public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemanticSequencer { | 20 | public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemanticSequencer { |
19 | @Inject | 21 | @Inject |
20 | private ProblemGrammarAccess grammarAccess; | 22 | private ProblemGrammarAccess grammarAccess; |
@@ -27,13 +29,13 @@ public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemant | |||
27 | return; | 29 | return; |
28 | } | 30 | } |
29 | if (errorAcceptor != null) { | 31 | if (errorAcceptor != null) { |
30 | if (transientValues.isValueTransient(semanticObject, ProblemPackage.Literals.ASSERTION__RELATION) == ValueTransient.YES) { | 32 | if (transientValues.isValueTransient(semanticObject, ABSTRACT_ASSERTION__RELATION) == ValueTransient.YES) { |
31 | errorAcceptor.accept(diagnosticProvider.createFeatureValueMissing(semanticObject, | 33 | errorAcceptor.accept(diagnosticProvider.createFeatureValueMissing(semanticObject, |
32 | ProblemPackage.Literals.ASSERTION__RELATION)); | 34 | ABSTRACT_ASSERTION__RELATION)); |
33 | } | 35 | } |
34 | if (transientValues.isListTransient(semanticObject, ProblemPackage.Literals.ASSERTION__ARGUMENTS) == ListTransient.YES) { | 36 | if (transientValues.isListTransient(semanticObject, ABSTRACT_ASSERTION__ARGUMENTS) == ListTransient.YES) { |
35 | errorAcceptor.accept(diagnosticProvider.createFeatureValueMissing(semanticObject, | 37 | errorAcceptor.accept(diagnosticProvider.createFeatureValueMissing(semanticObject, |
36 | ProblemPackage.Literals.ASSERTION__ARGUMENTS)); | 38 | ABSTRACT_ASSERTION__ARGUMENTS)); |
37 | } | 39 | } |
38 | } | 40 | } |
39 | var feeder = createSequencerFeeder(context, semanticObject); | 41 | var feeder = createSequencerFeeder(context, semanticObject); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java index 9e44063d..65721924 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java +++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java | |||
@@ -8,6 +8,7 @@ package tools.refinery.language.typesystem; | |||
8 | public sealed interface ExprType permits FixedType, MutableType { | 8 | public sealed interface ExprType permits FixedType, MutableType { |
9 | NodeType NODE = new NodeType(); | 9 | NodeType NODE = new NodeType(); |
10 | LiteralType LITERAL = new LiteralType(); | 10 | LiteralType LITERAL = new LiteralType(); |
11 | ModalLiteralType MODAL_LITERAL = new ModalLiteralType(); | ||
11 | InvalidType INVALID = new InvalidType(); | 12 | InvalidType INVALID = new InvalidType(); |
12 | 13 | ||
13 | FixedType getActualType(); | 14 | FixedType getActualType(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java index 1b2ded48..f9e75f26 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java +++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java | |||
@@ -5,7 +5,8 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.language.typesystem; | 6 | package tools.refinery.language.typesystem; |
7 | 7 | ||
8 | public sealed interface FixedType extends ExprType permits NodeType, LiteralType, InvalidType, DataExprType { | 8 | public sealed interface FixedType extends ExprType permits NodeType, LiteralType, ModalLiteralType, InvalidType, |
9 | DataExprType { | ||
9 | @Override | 10 | @Override |
10 | default FixedType getActualType() { | 11 | default FixedType getActualType() { |
11 | return this; | 12 | return this; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/ModalLiteralType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ModalLiteralType.java new file mode 100644 index 00000000..6b44977a --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ModalLiteralType.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.typesystem; | ||
7 | |||
8 | public final class ModalLiteralType implements FixedType { | ||
9 | ModalLiteralType() { | ||
10 | } | ||
11 | |||
12 | @Override | ||
13 | public String toString() { | ||
14 | return "modal constraint"; | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java index 6691f0ae..63815506 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java | |||
@@ -63,6 +63,7 @@ public class TypedModule { | |||
63 | for (var statement : problem.getStatements()) { | 63 | for (var statement : problem.getStatements()) { |
64 | switch (statement) { | 64 | switch (statement) { |
65 | case PredicateDefinition predicateDefinition -> checkTypes(predicateDefinition); | 65 | case PredicateDefinition predicateDefinition -> checkTypes(predicateDefinition); |
66 | case RuleDefinition ruleDefinition -> checkTypes(ruleDefinition); | ||
66 | case Assertion assertion -> checkTypes(assertion); | 67 | case Assertion assertion -> checkTypes(assertion); |
67 | default -> { | 68 | default -> { |
68 | // Nothing to type check. | 69 | // Nothing to type check. |
@@ -79,13 +80,33 @@ public class TypedModule { | |||
79 | } | 80 | } |
80 | } | 81 | } |
81 | 82 | ||
83 | private void checkTypes(RuleDefinition ruleDefinition) { | ||
84 | for (var conjunction : ruleDefinition.getPreconditions()) { | ||
85 | for (var literal : conjunction.getLiterals()) { | ||
86 | expectType(literal, ExprType.MODAL_LITERAL); | ||
87 | } | ||
88 | } | ||
89 | for (var consequent : ruleDefinition.getConsequents()) { | ||
90 | for (var action : consequent.getActions()) { | ||
91 | if (action instanceof AssertionAction assertionAction) { | ||
92 | checkTypes(assertionAction); | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
82 | private void checkTypes(Assertion assertion) { | 98 | private void checkTypes(Assertion assertion) { |
99 | checkAssertionValueType(assertion); | ||
100 | checkNodeAssertionArgumentTypes(assertion, false); | ||
101 | } | ||
102 | |||
103 | private void checkAssertionValueType(AbstractAssertion assertion) { | ||
83 | var relation = assertion.getRelation(); | 104 | var relation = assertion.getRelation(); |
84 | var value = assertion.getValue(); | ||
85 | if (relation == null) { | 105 | if (relation == null) { |
86 | return; | 106 | return; |
87 | } | 107 | } |
88 | var type = signatureProvider.getSignature(relation).resultType(); | 108 | var type = signatureProvider.getSignature(relation).resultType(); |
109 | var value = assertion.getValue(); | ||
89 | if (type == ExprType.LITERAL) { | 110 | if (type == ExprType.LITERAL) { |
90 | if (value == null) { | 111 | if (value == null) { |
91 | return; | 112 | return; |
@@ -95,11 +116,43 @@ public class TypedModule { | |||
95 | } | 116 | } |
96 | if (value == null) { | 117 | if (value == null) { |
97 | var message = "Assertion value of type %s is required.".formatted(type); | 118 | var message = "Assertion value of type %s is required.".formatted(type); |
98 | error(message, assertion, ProblemPackage.Literals.ASSERTION__RELATION, 0, ProblemValidator.TYPE_ERROR); | 119 | error(message, assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__VALUE, 0, |
120 | ProblemValidator.TYPE_ERROR); | ||
99 | } | 121 | } |
100 | expectType(value, type); | 122 | expectType(value, type); |
101 | } | 123 | } |
102 | 124 | ||
125 | private void checkNodeAssertionArgumentTypes(AbstractAssertion assertion, boolean allowVariables) { | ||
126 | for (var argument : assertion.getArguments()) { | ||
127 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument) { | ||
128 | checkNodeAssertionArgumentType(nodeAssertionArgument, allowVariables); | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | private void checkNodeAssertionArgumentType(NodeAssertionArgument nodeAssertionArgument, boolean allowVariables) { | ||
134 | var variableOrNode = nodeAssertionArgument.getNode(); | ||
135 | if (variableOrNode == null || variableOrNode.eIsProxy()) { | ||
136 | return; | ||
137 | } | ||
138 | if (allowVariables && variableOrNode instanceof Variable variable) { | ||
139 | var variableType = getVariableType(variable); | ||
140 | if (variableType == ExprType.INVALID || variableType == ExprType.NODE) { | ||
141 | return; | ||
142 | } | ||
143 | } | ||
144 | if (variableOrNode instanceof Node) { | ||
145 | return; | ||
146 | } | ||
147 | error("Assertion argument must be a node.", nodeAssertionArgument, | ||
148 | ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE, 0, ProblemValidator.TYPE_ERROR); | ||
149 | } | ||
150 | |||
151 | private void checkTypes(AssertionAction assertionAction) { | ||
152 | checkAssertionValueType(assertionAction); | ||
153 | checkNodeAssertionArgumentTypes(assertionAction, true); | ||
154 | } | ||
155 | |||
103 | public List<FeatureBasedDiagnostic> getDiagnostics() { | 156 | public List<FeatureBasedDiagnostic> getDiagnostics() { |
104 | return diagnostics; | 157 | return diagnostics; |
105 | } | 158 | } |
@@ -202,6 +255,7 @@ public class TypedModule { | |||
202 | case RangeExpr rangeExpr -> computeExpressionType(rangeExpr); | 255 | case RangeExpr rangeExpr -> computeExpressionType(rangeExpr); |
203 | case ArithmeticBinaryExpr arithmeticBinaryExpr -> computeExpressionType(arithmeticBinaryExpr); | 256 | case ArithmeticBinaryExpr arithmeticBinaryExpr -> computeExpressionType(arithmeticBinaryExpr); |
204 | case CastExpr castExpr -> computeExpressionType(castExpr); | 257 | case CastExpr castExpr -> computeExpressionType(castExpr); |
258 | case ModalExpr modalExpr -> computeExpressionType(modalExpr); | ||
205 | default -> { | 259 | default -> { |
206 | error("Unknown expression: " + expr.getClass().getSimpleName(), expr, null, 0, | 260 | error("Unknown expression: " + expr.getClass().getSimpleName(), expr, null, 0, |
207 | ProblemValidator.UNKNOWN_EXPRESSION_ISSUE); | 261 | ProblemValidator.UNKNOWN_EXPRESSION_ISSUE); |
@@ -290,6 +344,9 @@ public class TypedModule { | |||
290 | // Negation of literals yields another (non-enumerable) literal. | 344 | // Negation of literals yields another (non-enumerable) literal. |
291 | return ExprType.LITERAL; | 345 | return ExprType.LITERAL; |
292 | } | 346 | } |
347 | if (actualType == ExprType.MODAL_LITERAL) { | ||
348 | return ExprType.MODAL_LITERAL; | ||
349 | } | ||
293 | if (actualType == ExprType.INVALID) { | 350 | if (actualType == ExprType.INVALID) { |
294 | return ExprType.INVALID; | 351 | return ExprType.INVALID; |
295 | } | 352 | } |
@@ -507,6 +564,28 @@ public class TypedModule { | |||
507 | return ExprType.INVALID; | 564 | return ExprType.INVALID; |
508 | } | 565 | } |
509 | 566 | ||
567 | @NotNull | ||
568 | private ExprType computeExpressionType(ModalExpr expr) { | ||
569 | var body = expr.getBody(); | ||
570 | if (body == null) { | ||
571 | return ExprType.INVALID; | ||
572 | } | ||
573 | var actualType = getExpressionType(body); | ||
574 | if (actualType == ExprType.LITERAL || BuiltinTermInterpreter.BOOLEAN_TYPE.equals(actualType)) { | ||
575 | return ExprType.MODAL_LITERAL; | ||
576 | } | ||
577 | if (actualType == ExprType.INVALID) { | ||
578 | return ExprType.INVALID; | ||
579 | } | ||
580 | if (actualType instanceof MutableType) { | ||
581 | error(OPERAND_TYPE_ERROR_MESSAGE, body, null, 0, ProblemValidator.TYPE_ERROR); | ||
582 | return ExprType.INVALID; | ||
583 | } | ||
584 | var message = "Data type %s does not support modal operators.".formatted(actualType); | ||
585 | error(message, expr, null, 0, ProblemValidator.TYPE_ERROR); | ||
586 | return ExprType.INVALID; | ||
587 | } | ||
588 | |||
510 | private FixedType getCommonDataType(BinaryExpr expr) { | 589 | private FixedType getCommonDataType(BinaryExpr expr) { |
511 | var commonType = getCommonType(expr); | 590 | var commonType = getCommonType(expr); |
512 | if (!(commonType instanceof DataExprType) && commonType != ExprType.INVALID) { | 591 | if (!(commonType instanceof DataExprType) && commonType != ExprType.INVALID) { |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java index 9daa8f61..e1820261 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java | |||
@@ -42,13 +42,11 @@ public final class ProblemUtil { | |||
42 | } | 42 | } |
43 | 43 | ||
44 | public static boolean isImplicit(EObject eObject) { | 44 | public static boolean isImplicit(EObject eObject) { |
45 | if (eObject instanceof Node node) { | 45 | return switch (eObject) { |
46 | return isImplicitNode(node); | 46 | case Node node -> isImplicitNode(node); |
47 | } else if (eObject instanceof Variable variable) { | 47 | case Variable variable -> isImplicitVariable(variable); |
48 | return isImplicitVariable(variable); | 48 | default -> false; |
49 | } else { | 49 | }; |
50 | return false; | ||
51 | } | ||
52 | } | 50 | } |
53 | 51 | ||
54 | public static boolean isError(EObject eObject) { | 52 | public static boolean isError(EObject eObject) { |
@@ -119,7 +117,7 @@ public final class ProblemUtil { | |||
119 | return false; | 117 | return false; |
120 | } | 118 | } |
121 | return switch (kind) { | 119 | return switch (kind) { |
122 | case CONTAINMENT -> false; | 120 | case CONTAINMENT, PARTIAL -> false; |
123 | case CONTAINER -> true; | 121 | case CONTAINER -> true; |
124 | case DEFAULT, REFERENCE -> { | 122 | case DEFAULT, REFERENCE -> { |
125 | var opposite = referenceDeclaration.getOpposite(); | 123 | var opposite = referenceDeclaration.getOpposite(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ActionTargetCollector.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ActionTargetCollector.java new file mode 100644 index 00000000..67cd5777 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ActionTargetCollector.java | |||
@@ -0,0 +1,73 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.validation; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Singleton; | ||
10 | import org.eclipse.xtext.EcoreUtil2; | ||
11 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
12 | import org.eclipse.xtext.util.Tuples; | ||
13 | import tools.refinery.language.model.problem.*; | ||
14 | |||
15 | import java.util.HashSet; | ||
16 | import java.util.Set; | ||
17 | |||
18 | @Singleton | ||
19 | public class ActionTargetCollector { | ||
20 | private static final String ACTION_TARGETS = | ||
21 | "tools.refinery.language.validation.ActionTargetCollector.ACTION_TARGETS"; | ||
22 | |||
23 | @Inject | ||
24 | private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; | ||
25 | |||
26 | public boolean isActionTarget(Relation relation) { | ||
27 | var problem = EcoreUtil2.getContainerOfType(relation, Problem.class); | ||
28 | return problem != null && isActionTarget(problem, relation); | ||
29 | } | ||
30 | |||
31 | public boolean isActionTarget(Problem problem, Relation relation) { | ||
32 | var targets = getActionTargets(problem); | ||
33 | if (targets.contains(relation)) { | ||
34 | return true; | ||
35 | } | ||
36 | if (relation instanceof ReferenceDeclaration referenceDeclaration) { | ||
37 | var opposite = referenceDeclaration.getOpposite(); | ||
38 | return opposite != null && targets.contains(opposite); | ||
39 | } | ||
40 | return false; | ||
41 | } | ||
42 | |||
43 | protected Set<Relation> getActionTargets(Problem problem) { | ||
44 | var resource = problem.eResource(); | ||
45 | if (resource == null) { | ||
46 | return doGetActionTargets(problem); | ||
47 | } | ||
48 | return cache.get(Tuples.create(problem, ACTION_TARGETS), resource, () -> doGetActionTargets(problem)); | ||
49 | } | ||
50 | |||
51 | protected Set<Relation> doGetActionTargets(Problem problem) { | ||
52 | var targets = new HashSet<Relation>(); | ||
53 | for (var statement : problem.getStatements()) { | ||
54 | if (statement instanceof RuleDefinition ruleDefinition) { | ||
55 | collectTargets(ruleDefinition, targets); | ||
56 | } | ||
57 | } | ||
58 | return targets; | ||
59 | } | ||
60 | |||
61 | private static void collectTargets(RuleDefinition ruleDefinition, HashSet<Relation> targets) { | ||
62 | for (var consequent : ruleDefinition.getConsequents()) { | ||
63 | for (var action : consequent.getActions()) { | ||
64 | if (action instanceof AssertionAction assertionAction) { | ||
65 | var target = assertionAction.getRelation(); | ||
66 | if (target != null) { | ||
67 | targets.add(target); | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ExistsVariableCollector.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ExistsVariableCollector.java new file mode 100644 index 00000000..5812bdc4 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ExistsVariableCollector.java | |||
@@ -0,0 +1,75 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.validation; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Singleton; | ||
10 | import org.eclipse.xtext.EcoreUtil2; | ||
11 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
12 | import org.eclipse.xtext.util.Tuples; | ||
13 | import tools.refinery.language.model.problem.*; | ||
14 | import tools.refinery.language.scoping.imports.ImportAdapterProvider; | ||
15 | import tools.refinery.language.utils.ProblemUtil; | ||
16 | |||
17 | import java.util.HashSet; | ||
18 | import java.util.Set; | ||
19 | |||
20 | @Singleton | ||
21 | public class ExistsVariableCollector { | ||
22 | private static final String EXISTS_VARIABLES = | ||
23 | "tools.refinery.language.validation.ExistsVariableCollector.EXISTS_VARIABLES"; | ||
24 | |||
25 | @Inject | ||
26 | private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; | ||
27 | |||
28 | @Inject | ||
29 | private ImportAdapterProvider importAdapterProvider; | ||
30 | |||
31 | public boolean missingExistsConstraint(Variable variable) { | ||
32 | if (variable instanceof Parameter) { | ||
33 | return false; | ||
34 | } | ||
35 | if (ProblemUtil.isImplicitVariable(variable)) { | ||
36 | var negation = EcoreUtil2.getContainerOfType(variable, NegationExpr.class); | ||
37 | if (negation != null) { | ||
38 | // Negations are implicitly quantified. | ||
39 | return false; | ||
40 | } | ||
41 | } | ||
42 | var conjunction = EcoreUtil2.getContainerOfType(variable, Conjunction.class); | ||
43 | if (conjunction == null) { | ||
44 | return true; | ||
45 | } | ||
46 | var variables = getExistsVariables(conjunction); | ||
47 | return !variables.contains(variable); | ||
48 | } | ||
49 | |||
50 | protected Set<Variable> getExistsVariables(Conjunction conjunction) { | ||
51 | var resource = conjunction.eResource(); | ||
52 | if (resource == null) { | ||
53 | return doGetExistsVariables(conjunction); | ||
54 | } | ||
55 | return cache.get(Tuples.create(conjunction, EXISTS_VARIABLES), resource, | ||
56 | () -> doGetExistsVariables(conjunction)); | ||
57 | } | ||
58 | |||
59 | protected Set<Variable> doGetExistsVariables(Conjunction conjunction) { | ||
60 | var builtinSymbols = importAdapterProvider.getBuiltinSymbols(conjunction); | ||
61 | var existsRelation = builtinSymbols.exists(); | ||
62 | var set = new HashSet<Variable>(); | ||
63 | for (var atom : EcoreUtil2.getAllContentsOfType(conjunction, Atom.class)) { | ||
64 | if (existsRelation.equals(atom.getRelation())) { | ||
65 | for (var argument : atom.getArguments()) { | ||
66 | if (argument instanceof VariableOrNodeExpr variableOrNodeExpr && | ||
67 | variableOrNodeExpr.getVariableOrNode() instanceof Variable variable) { | ||
68 | set.add(variable); | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | return set; | ||
74 | } | ||
75 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index 745e2d2b..84218f17 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -24,10 +24,7 @@ import tools.refinery.language.typesystem.ProblemTypeAnalyzer; | |||
24 | import tools.refinery.language.typesystem.SignatureProvider; | 24 | import tools.refinery.language.typesystem.SignatureProvider; |
25 | import tools.refinery.language.utils.ProblemUtil; | 25 | import tools.refinery.language.utils.ProblemUtil; |
26 | 26 | ||
27 | import java.util.ArrayList; | 27 | import java.util.*; |
28 | import java.util.LinkedHashMap; | ||
29 | import java.util.LinkedHashSet; | ||
30 | import java.util.Set; | ||
31 | 28 | ||
32 | /** | 29 | /** |
33 | * This class contains custom validation rules. | 30 | * This class contains custom validation rules. |
@@ -49,17 +46,26 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
49 | public static final String INVALID_SUPERTYPE_ISSUE = ISSUE_PREFIX + "INVALID_SUPERTYPE"; | 46 | public static final String INVALID_SUPERTYPE_ISSUE = ISSUE_PREFIX + "INVALID_SUPERTYPE"; |
50 | public static final String INVALID_REFERENCE_TYPE_ISSUE = ISSUE_PREFIX + "INVALID_REFERENCE_TYPE"; | 47 | public static final String INVALID_REFERENCE_TYPE_ISSUE = ISSUE_PREFIX + "INVALID_REFERENCE_TYPE"; |
51 | public static final String INVALID_ARITY_ISSUE = ISSUE_PREFIX + "INVALID_ARITY"; | 48 | public static final String INVALID_ARITY_ISSUE = ISSUE_PREFIX + "INVALID_ARITY"; |
49 | public static final String INVALID_MODALITY_ISSUE = ISSUE_PREFIX + "INVALID_MODALITY"; | ||
50 | public static final String INVALID_RULE_ISSUE = ISSUE_PREFIX + "INVALID_RULE"; | ||
52 | public static final String INVALID_TRANSITIVE_CLOSURE_ISSUE = ISSUE_PREFIX + "INVALID_TRANSITIVE_CLOSURE"; | 51 | public static final String INVALID_TRANSITIVE_CLOSURE_ISSUE = ISSUE_PREFIX + "INVALID_TRANSITIVE_CLOSURE"; |
53 | public static final String INVALID_VALUE_ISSUE = ISSUE_PREFIX + "INVALID_VALUE"; | ||
54 | public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION"; | 52 | public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION"; |
55 | public static final String UNKNOWN_EXPRESSION_ISSUE = ISSUE_PREFIX + "UNKNOWN_EXPRESSION"; | 53 | public static final String UNKNOWN_EXPRESSION_ISSUE = ISSUE_PREFIX + "UNKNOWN_EXPRESSION"; |
56 | public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT"; | 54 | public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT"; |
57 | public static final String TYPE_ERROR = ISSUE_PREFIX + "TYPE_ERROR"; | 55 | public static final String TYPE_ERROR = ISSUE_PREFIX + "TYPE_ERROR"; |
56 | public static final String VARIABLE_WITHOUT_EXISTS = ISSUE_PREFIX + "VARIABLE_WITHOUT_EXISTS"; | ||
57 | public static final String UNUSED_PARTIAL_RELATION = ISSUE_PREFIX + "UNUSED_PARTIAL_RELATION"; | ||
58 | 58 | ||
59 | @Inject | 59 | @Inject |
60 | private ReferenceCounter referenceCounter; | 60 | private ReferenceCounter referenceCounter; |
61 | 61 | ||
62 | @Inject | 62 | @Inject |
63 | private ExistsVariableCollector existsVariableCollector; | ||
64 | |||
65 | @Inject | ||
66 | private ActionTargetCollector actionTargetCollector; | ||
67 | |||
68 | @Inject | ||
63 | private IQualifiedNameConverter qualifiedNameConverter; | 69 | private IQualifiedNameConverter qualifiedNameConverter; |
64 | 70 | ||
65 | @Inject | 71 | @Inject |
@@ -128,6 +134,14 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
128 | var variableOrNode = expr.getVariableOrNode(); | 134 | var variableOrNode = expr.getVariableOrNode(); |
129 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) | 135 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) |
130 | && !ProblemUtil.isSingletonVariable(variable)) { | 136 | && !ProblemUtil.isSingletonVariable(variable)) { |
137 | if (EcoreUtil2.getContainerOfType(variable, ParametricDefinition.class) instanceof RuleDefinition && | ||
138 | EcoreUtil2.getContainerOfType(variable, NegationExpr.class) == null && | ||
139 | // If there is an exists constraint, it is the only constraint. | ||
140 | existsVariableCollector.missingExistsConstraint(variable)) { | ||
141 | // Existentially quantified variables in rules should not be singletons, | ||
142 | // because we have to add an {@code exists} constraint as well. | ||
143 | return; | ||
144 | } | ||
131 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); | 145 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); |
132 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { | 146 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { |
133 | var name = variable.getName(); | 147 | var name = variable.getName(); |
@@ -152,13 +166,32 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
152 | } | 166 | } |
153 | 167 | ||
154 | @Check | 168 | @Check |
169 | public void checkNodeAssertionArgumentConstants(NodeAssertionArgument argument) { | ||
170 | var rule = EcoreUtil2.getContainerOfType(argument, RuleDefinition.class); | ||
171 | if (rule == null) { | ||
172 | return; | ||
173 | } | ||
174 | var variableOrNode = argument.getNode(); | ||
175 | if (variableOrNode instanceof Node node && !ProblemUtil.isAtomNode(node)) { | ||
176 | var name = node.getName(); | ||
177 | var message = ("Only atoms can be referenced in rule actions. " + | ||
178 | "Mark '%s' as an atom with the declaration 'atom %s.'").formatted(name, name); | ||
179 | error(message, argument, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE, | ||
180 | INSIGNIFICANT_INDEX, NODE_CONSTANT_ISSUE); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | @Check | ||
155 | public void checkUniqueDeclarations(Problem problem) { | 185 | public void checkUniqueDeclarations(Problem problem) { |
156 | var relations = new ArrayList<Relation>(); | 186 | var relations = new ArrayList<NamedElement>(); |
157 | var nodes = new ArrayList<Node>(); | 187 | var nodes = new ArrayList<Node>(); |
158 | var aggregators = new ArrayList<AggregatorDeclaration>(); | 188 | var aggregators = new ArrayList<AggregatorDeclaration>(); |
159 | for (var statement : problem.getStatements()) { | 189 | for (var statement : problem.getStatements()) { |
160 | if (statement instanceof Relation relation) { | 190 | if (statement instanceof Relation relation) { |
161 | relations.add(relation); | 191 | relations.add(relation); |
192 | } else if (statement instanceof RuleDefinition ruleDefinition) { | ||
193 | // Rule definitions and predicates live in the same namespace. | ||
194 | relations.add(ruleDefinition); | ||
162 | } else if (statement instanceof NodeDeclaration nodeDeclaration) { | 195 | } else if (statement instanceof NodeDeclaration nodeDeclaration) { |
163 | nodes.addAll(nodeDeclaration.getNodes()); | 196 | nodes.addAll(nodeDeclaration.getNodes()); |
164 | } else if (statement instanceof AggregatorDeclaration aggregatorDeclaration) { | 197 | } else if (statement instanceof AggregatorDeclaration aggregatorDeclaration) { |
@@ -298,6 +331,22 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
298 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, | 331 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, |
299 | INVALID_OPPOSITE_ISSUE); | 332 | INVALID_OPPOSITE_ISSUE); |
300 | } | 333 | } |
334 | } else if (kind == ReferenceKind.PARTIAL && opposite != null && opposite.getKind() != ReferenceKind.PARTIAL) { | ||
335 | acceptError("Opposite '%s' of partial reference '%s' is not a partial reference." | ||
336 | .formatted(opposite.getName(), referenceDeclaration.getName()), | ||
337 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, | ||
338 | INVALID_OPPOSITE_ISSUE); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | @Check | ||
343 | public void checkPartialReference(ReferenceDeclaration referenceDeclaration) { | ||
344 | if (referenceDeclaration.getKind() == ReferenceKind.PARTIAL && | ||
345 | !actionTargetCollector.isActionTarget(referenceDeclaration)) { | ||
346 | var message = "Add decision or propagation rules to refine partial relation '%s'." | ||
347 | .formatted(referenceDeclaration.getName()); | ||
348 | acceptWarning(message, referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__KIND, 0, | ||
349 | UNUSED_PARTIAL_RELATION); | ||
301 | } | 350 | } |
302 | } | 351 | } |
303 | 352 | ||
@@ -336,8 +385,33 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
336 | } | 385 | } |
337 | 386 | ||
338 | @Check | 387 | @Check |
339 | public void checkParameterType(Parameter parameter) { | 388 | public void checkParameter(Parameter parameter) { |
340 | checkArity(parameter, ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 1); | 389 | checkArity(parameter, ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 1); |
390 | var parametricDefinition = EcoreUtil2.getContainerOfType(parameter, ParametricDefinition.class); | ||
391 | if (parametricDefinition instanceof RuleDefinition rule) { | ||
392 | if (parameter.getParameterType() != null && parameter.getModality() == Modality.NONE) { | ||
393 | acceptError("Parameter type modality must be specified.", parameter, | ||
394 | ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 0, INVALID_MODALITY_ISSUE); | ||
395 | } | ||
396 | var kind = rule.getKind(); | ||
397 | var binding = parameter.getBinding(); | ||
398 | if (kind == RuleKind.PROPAGATION && binding != ParameterBinding.SINGLE) { | ||
399 | acceptError("Parameter binding annotations are not supported in propagation rules.", parameter, | ||
400 | ProblemPackage.Literals.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); | ||
401 | } else if (kind != RuleKind.DECISION && binding == ParameterBinding.MULTI) { | ||
402 | acceptError("Explicit multi-object bindings are only supported in decision rules.", parameter, | ||
403 | ProblemPackage.Literals.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); | ||
404 | } | ||
405 | } else { | ||
406 | if (parameter.getConcreteness() != Concreteness.PARTIAL || parameter.getModality() != Modality.NONE) { | ||
407 | acceptError("Modal parameter types are only supported in rule definitions.", parameter, null, 0, | ||
408 | INVALID_MODALITY_ISSUE); | ||
409 | } | ||
410 | if (parameter.getBinding() != ParameterBinding.SINGLE) { | ||
411 | acceptError("Parameter binding annotations are only supported in decision rules.", parameter, | ||
412 | ProblemPackage.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); | ||
413 | } | ||
414 | } | ||
341 | } | 415 | } |
342 | 416 | ||
343 | @Check | 417 | @Check |
@@ -353,16 +427,46 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
353 | } | 427 | } |
354 | 428 | ||
355 | @Check | 429 | @Check |
356 | public void checkAssertion(Assertion assertion) { | 430 | public void checkRuleDefinition(RuleDefinition ruleDefinition) { |
431 | if (ruleDefinition.getConsequents().size() != 1) { | ||
432 | acceptError("Rules must have exactly one consequent.", ruleDefinition, | ||
433 | ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, INVALID_RULE_ISSUE); | ||
434 | } | ||
435 | var unquantifiedVariables = new HashSet<Variable>(); | ||
436 | for (var variable : EcoreUtil2.getAllContentsOfType(ruleDefinition, Variable.class)) { | ||
437 | if (existsVariableCollector.missingExistsConstraint(variable)) { | ||
438 | unquantifiedVariables.add(variable); | ||
439 | } | ||
440 | } | ||
441 | for (var expr : EcoreUtil2.getAllContentsOfType(ruleDefinition, VariableOrNodeExpr.class)) { | ||
442 | if (expr.getVariableOrNode() instanceof Variable variable && unquantifiedVariables.contains(variable)) { | ||
443 | unquantifiedVariables.remove(variable); | ||
444 | var name = variable.getName(); | ||
445 | String message; | ||
446 | if (ProblemUtil.isSingletonVariable(variable)) { | ||
447 | message = ("Remove the singleton variable marker '_' and clarify the quantification of variable " + | ||
448 | "'%s'.").formatted(name); | ||
449 | } else { | ||
450 | message = ("Add a 'must exists(%s)', 'may exists(%s)', or 'may !exists(%s)' constraint to " + | ||
451 | "clarify the quantification of variable '%s'.").formatted(name, name, name, name); | ||
452 | } | ||
453 | acceptWarning(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, 0, | ||
454 | VARIABLE_WITHOUT_EXISTS); | ||
455 | } | ||
456 | } | ||
457 | } | ||
458 | |||
459 | @Check | ||
460 | public void checkAssertion(AbstractAssertion assertion) { | ||
357 | var relation = assertion.getRelation(); | 461 | var relation = assertion.getRelation(); |
358 | if (relation instanceof DatatypeDeclaration) { | 462 | if (relation instanceof DatatypeDeclaration) { |
359 | var message = "Assertions for data types are not supported."; | 463 | var message = "Assertions for data types are not supported."; |
360 | acceptError(message, assertion, ProblemPackage.Literals.ASSERTION__RELATION, 0, | 464 | acceptError(message, assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, 0, |
361 | UNSUPPORTED_ASSERTION_ISSUE); | 465 | UNSUPPORTED_ASSERTION_ISSUE); |
362 | return; | 466 | return; |
363 | } | 467 | } |
364 | int argumentCount = assertion.getArguments().size(); | 468 | int argumentCount = assertion.getArguments().size(); |
365 | checkArity(assertion, ProblemPackage.Literals.ASSERTION__RELATION, argumentCount); | 469 | checkArity(assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, argumentCount); |
366 | } | 470 | } |
367 | 471 | ||
368 | @Check | 472 | @Check |
@@ -466,15 +570,17 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
466 | 570 | ||
467 | @Nullable | 571 | @Nullable |
468 | private Node getNodeArgumentForMultiObjectAssertion(AssertionArgument argument) { | 572 | private Node getNodeArgumentForMultiObjectAssertion(AssertionArgument argument) { |
469 | if (argument instanceof WildcardAssertionArgument) { | 573 | return switch (argument) { |
470 | acceptError("Wildcard arguments for 'exists' are not supported.", argument, null, 0, | 574 | case null -> null; |
471 | UNSUPPORTED_ASSERTION_ISSUE); | 575 | case WildcardAssertionArgument ignoredWildcardAssertionArgument -> { |
472 | return null; | 576 | acceptError("Wildcard arguments for 'exists' are not supported.", argument, null, 0, |
473 | } | 577 | UNSUPPORTED_ASSERTION_ISSUE); |
474 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument) { | 578 | yield null; |
475 | return nodeAssertionArgument.getNode(); | 579 | } |
476 | } | 580 | case NodeAssertionArgument nodeAssertionArgument -> |
477 | throw new IllegalArgumentException("Unknown assertion argument: " + argument); | 581 | nodeAssertionArgument.getNode() instanceof Node node ? node : null; |
582 | default -> throw new IllegalArgumentException("Unknown assertion argument: " + argument); | ||
583 | }; | ||
478 | } | 584 | } |
479 | 585 | ||
480 | @Check | 586 | @Check |
@@ -484,8 +590,10 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
484 | } | 590 | } |
485 | for (var argument : assertion.getArguments()) { | 591 | for (var argument : assertion.getArguments()) { |
486 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument) { | 592 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument) { |
487 | var node = nodeAssertionArgument.getNode(); | 593 | var variableOrNode = nodeAssertionArgument.getNode(); |
488 | if (node != null && !node.eIsProxy() && ProblemUtil.isImplicitNode(node)) { | 594 | if (variableOrNode instanceof Node node && |
595 | !variableOrNode.eIsProxy() && | ||
596 | ProblemUtil.isImplicitNode(node)) { | ||
489 | var name = node.getName(); | 597 | var name = node.getName(); |
490 | var message = ("Implicit nodes are not allowed in modules. Explicitly declare '%s' as a node " + | 598 | var message = ("Implicit nodes are not allowed in modules. Explicitly declare '%s' as a node " + |
491 | "with the declaration 'declare %s.'").formatted(name, name); | 599 | "with the declaration 'declare %s.'").formatted(name, name); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java index 55cbd71d..40781168 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ReferenceCounter.java | |||
@@ -5,20 +5,21 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.language.validation; | 6 | package tools.refinery.language.validation; |
7 | 7 | ||
8 | import java.util.HashMap; | 8 | import com.google.inject.Inject; |
9 | import java.util.Map; | 9 | import com.google.inject.Singleton; |
10 | |||
11 | import org.eclipse.emf.ecore.EObject; | 10 | import org.eclipse.emf.ecore.EObject; |
12 | import org.eclipse.xtext.util.IResourceScopeCache; | 11 | import org.eclipse.xtext.util.IResourceScopeCache; |
13 | import org.eclipse.xtext.util.Tuples; | 12 | import org.eclipse.xtext.util.Tuples; |
14 | |||
15 | import com.google.inject.Inject; | ||
16 | import com.google.inject.Singleton; | ||
17 | |||
18 | import tools.refinery.language.model.problem.Problem; | 13 | import tools.refinery.language.model.problem.Problem; |
19 | 14 | ||
15 | import java.util.HashMap; | ||
16 | import java.util.Map; | ||
17 | |||
20 | @Singleton | 18 | @Singleton |
21 | public class ReferenceCounter { | 19 | public class ReferenceCounter { |
20 | private static final String REFERENCE_COUNTS = | ||
21 | "tools.refinery.language.validation.ReferenceCounter.REFERENCE_COUNTS"; | ||
22 | |||
22 | @Inject | 23 | @Inject |
23 | private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; | 24 | private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; |
24 | 25 | ||
@@ -35,7 +36,7 @@ public class ReferenceCounter { | |||
35 | if (resource == null) { | 36 | if (resource == null) { |
36 | return doGetReferenceCounts(problem); | 37 | return doGetReferenceCounts(problem); |
37 | } | 38 | } |
38 | return cache.get(Tuples.create(problem, "referenceCounts"), resource, () -> doGetReferenceCounts(problem)); | 39 | return cache.get(Tuples.create(problem, REFERENCE_COUNTS), resource, () -> doGetReferenceCounts(problem)); |
39 | } | 40 | } |
40 | 41 | ||
41 | protected Map<EObject, Integer> doGetReferenceCounts(Problem problem) { | 42 | protected Map<EObject, Integer> doGetReferenceCounts(Problem problem) { |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java index 56e65550..15636483 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java | |||
@@ -9,7 +9,6 @@ import com.google.inject.Inject; | |||
9 | import org.eclipse.xtext.testing.InjectWith; | 9 | import org.eclipse.xtext.testing.InjectWith; |
10 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | 10 | import org.eclipse.xtext.testing.extensions.InjectionExtension; |
11 | import org.junit.jupiter.api.Disabled; | 11 | import org.junit.jupiter.api.Disabled; |
12 | import org.junit.jupiter.api.Test; | ||
13 | import org.junit.jupiter.api.extension.ExtendWith; | 12 | import org.junit.jupiter.api.extension.ExtendWith; |
14 | import org.junit.jupiter.params.ParameterizedTest; | 13 | import org.junit.jupiter.params.ParameterizedTest; |
15 | import org.junit.jupiter.params.provider.ValueSource; | 14 | import org.junit.jupiter.params.provider.ValueSource; |
@@ -17,7 +16,7 @@ import tools.refinery.language.model.tests.utils.ProblemParseHelper; | |||
17 | import tools.refinery.language.tests.ProblemInjectorProvider; | 16 | import tools.refinery.language.tests.ProblemInjectorProvider; |
18 | 17 | ||
19 | import static org.hamcrest.MatcherAssert.assertThat; | 18 | import static org.hamcrest.MatcherAssert.assertThat; |
20 | import static org.hamcrest.Matchers.*; | 19 | import static org.hamcrest.Matchers.empty; |
21 | 20 | ||
22 | @Disabled("TODO: Rework transformation rules") | 21 | @Disabled("TODO: Rework transformation rules") |
23 | @ExtendWith(InjectionExtension.class) | 22 | @ExtendWith(InjectionExtension.class) |
@@ -29,62 +28,13 @@ class RuleParsingTest { | |||
29 | @ParameterizedTest | 28 | @ParameterizedTest |
30 | @ValueSource(strings = { """ | 29 | @ValueSource(strings = { """ |
31 | pred Person(p). | 30 | pred Person(p). |
32 | rule r(p1): must Person(p1) ==> Person(p1) := false. | 31 | rule r(p1): must Person(p1) ==> Person(p1): false. |
33 | """, """ | ||
34 | pred Person(p). | ||
35 | rule r(p1): must Person(p1) ==> Person(p1) <: false. | ||
36 | """, """ | 32 | """, """ |
37 | pred Person(p). | 33 | pred Person(p). |
38 | rule r(p1): must Person(p1) ==> !Person(p1). | 34 | rule r(p1): must Person(p1) ==> !Person(p1). |
39 | """, """ | ||
40 | pred Person(p). | ||
41 | rule r(p1): must Person(p1) ==> delete p1. | ||
42 | """ }) | 35 | """ }) |
43 | void simpleTest(String text) { | 36 | void simpleTest(String text) { |
44 | var problem = parseHelper.parse(text); | 37 | var problem = parseHelper.parse(text); |
45 | assertThat(problem.getResourceErrors(), empty()); | 38 | assertThat(problem.getResourceErrors(), empty()); |
46 | } | 39 | } |
47 | |||
48 | @Test | ||
49 | void newNodeTest() { | ||
50 | var problem = parseHelper.parse(""" | ||
51 | pred Person(p). | ||
52 | rule r(p1): must Person(p1) ==> new p2, Person(p2) := unknown. | ||
53 | """); | ||
54 | assertThat(problem.getResourceErrors(), empty()); | ||
55 | assertThat(problem.rule("r").param(0), equalTo(problem.rule("r").conj(0).lit(0).arg(0).variable())); | ||
56 | assertThat(problem.rule("r").consequent(0).action(0).newVar(), | ||
57 | equalTo(problem.rule("r").consequent(0).action(1).assertedAtom().arg(0).variable())); | ||
58 | } | ||
59 | |||
60 | @Test | ||
61 | void differentScopeTest() { | ||
62 | var problem = parseHelper.parse(""" | ||
63 | pred Friend(a, b). | ||
64 | rule r(p1): !may Friend(p1, p2) ==> new p2, Friend(p1, p2) := true. | ||
65 | """); | ||
66 | assertThat(problem.getResourceErrors(), empty()); | ||
67 | assertThat(problem.rule("r").conj(0).lit(0).negated().arg(1).variable(), | ||
68 | not(equalTo(problem.rule("r").consequent(0).action(1).assertedAtom().arg(1).variable()))); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void parameterShadowingTest() { | ||
73 | var problem = parseHelper.parse(""" | ||
74 | pred Friend(a, b). | ||
75 | rule r(p1, p2): !may Friend(p1, p2) ==> new p2, Friend(p1, p2) := true. | ||
76 | """); | ||
77 | assertThat(problem.getResourceErrors(), empty()); | ||
78 | assertThat(problem.rule("r").param(1), | ||
79 | not(equalTo(problem.rule("r").consequent(0).action(1).assertedAtom().arg(1).variable()))); | ||
80 | } | ||
81 | |||
82 | @Test | ||
83 | void deleteDifferentScopeNodeTest() { | ||
84 | var problem = parseHelper.parse(""" | ||
85 | pred Person(p). | ||
86 | rule r(p1): must Friend(p1, p2) ==> delete p2. | ||
87 | """); | ||
88 | assertThat(problem.getResourceErrors(), not(empty())); | ||
89 | } | ||
90 | } | 40 | } |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java index 57602377..d73ef8e7 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java | |||
@@ -57,6 +57,18 @@ class OppositeValidationTest { | |||
57 | class Foo { | 57 | class Foo { |
58 | Foo foo[] opposite foo | 58 | Foo foo[] opposite foo |
59 | } | 59 | } |
60 | """, """ | ||
61 | class Foo { | ||
62 | partial Bar bar opposite foo | ||
63 | } | ||
64 | |||
65 | class Bar { | ||
66 | partial Foo foo opposite bar | ||
67 | } | ||
68 | """, """ | ||
69 | class Foo { | ||
70 | partial Foo foo[] opposite foo | ||
71 | } | ||
60 | """}) | 72 | """}) |
61 | void validOppositeTest(String text) { | 73 | void validOppositeTest(String text) { |
62 | var problem = parseHelper.parse(text); | 74 | var problem = parseHelper.parse(text); |
@@ -188,7 +200,7 @@ class OppositeValidationTest { | |||
188 | } | 200 | } |
189 | 201 | ||
190 | @ParameterizedTest | 202 | @ParameterizedTest |
191 | @ValueSource(strings = {"Foo foo", "container Foo foo"}) | 203 | @ValueSource(strings = {"Foo foo", "container Foo foo", "partial Foo foo"}) |
192 | void containerInvalidOppositeTest(String reference) { | 204 | void containerInvalidOppositeTest(String reference) { |
193 | var problem = parseHelper.parse(""" | 205 | var problem = parseHelper.parse(""" |
194 | class Foo { | 206 | class Foo { |
@@ -203,7 +215,27 @@ class OppositeValidationTest { | |||
203 | assertThat(issues, hasItem(allOf( | 215 | assertThat(issues, hasItem(allOf( |
204 | hasProperty("severity", is(Diagnostic.ERROR)), | 216 | hasProperty("severity", is(Diagnostic.ERROR)), |
205 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), | 217 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), |
206 | hasProperty("message", stringContainsInOrder("foo", "bar")) | 218 | hasProperty("message", stringContainsInOrder("foo", "container", "bar")) |
219 | ))); | ||
220 | } | ||
221 | |||
222 | @ParameterizedTest | ||
223 | @ValueSource(strings = {"Foo foo", "contains Foo foo", "container Foo foo"}) | ||
224 | void partialWithConcreteOppositeTest(String reference) { | ||
225 | var problem = parseHelper.parse(""" | ||
226 | class Foo { | ||
227 | partial Bar bar opposite foo | ||
228 | } | ||
229 | |||
230 | class Bar { | ||
231 | %s opposite bar | ||
232 | } | ||
233 | """.formatted(reference)); | ||
234 | var issues = problem.validate(); | ||
235 | assertThat(issues, hasItem(allOf( | ||
236 | hasProperty("severity", is(Diagnostic.ERROR)), | ||
237 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), | ||
238 | hasProperty("message", stringContainsInOrder("foo", "partial", "bar")) | ||
207 | ))); | 239 | ))); |
208 | } | 240 | } |
209 | } | 241 | } |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java index 3a49a0b9..4987cb89 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java | |||
@@ -6,25 +6,9 @@ | |||
6 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
7 | 7 | ||
8 | import tools.refinery.language.model.problem.Action; | 8 | import tools.refinery.language.model.problem.Action; |
9 | import tools.refinery.language.model.problem.AssertionAction; | ||
10 | import tools.refinery.language.model.problem.DeleteAction; | ||
11 | import tools.refinery.language.model.problem.NewAction; | ||
12 | import tools.refinery.language.model.problem.VariableOrNode; | ||
13 | 9 | ||
14 | public record WrappedAction(Action action) { | 10 | public record WrappedAction(Action action) { |
15 | public Action get() { | 11 | public Action get() { |
16 | return action; | 12 | return action; |
17 | } | 13 | } |
18 | |||
19 | public VariableOrNode newVar() { | ||
20 | return ((NewAction) action).getVariable(); | ||
21 | } | ||
22 | |||
23 | public VariableOrNode deleteVar() { | ||
24 | return ((DeleteAction) action).getVariableOrNode(); | ||
25 | } | ||
26 | |||
27 | public WrappedAtom assertedAtom() { | ||
28 | return new WrappedAtom(((AssertionAction) action).getAtom()); | ||
29 | } | ||
30 | } | 14 | } |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java index b36f2506..50cb958d 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java | |||
@@ -15,6 +15,6 @@ public record WrappedAssertionArgument(AssertionArgument assertionArgument) { | |||
15 | } | 15 | } |
16 | 16 | ||
17 | public Node node() { | 17 | public Node node() { |
18 | return ((NodeAssertionArgument) assertionArgument).getNode(); | 18 | return (Node) ((NodeAssertionArgument) assertionArgument).getNode(); |
19 | } | 19 | } |
20 | } | 20 | } |