diff options
Diffstat (limited to 'subprojects/language/src/main')
9 files changed, 159 insertions, 171 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 10e994a0..7e3b3c83 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -58,6 +58,7 @@ ReferenceMultiplicity returns Multiplicity: | |||
58 | "[" Multiplicity "]"; | 58 | "[" Multiplicity "]"; |
59 | 59 | ||
60 | PredicateDefinition: | 60 | PredicateDefinition: |
61 | shadow?="shadow"? | ||
61 | ("pred" | error?="error" "pred"?) | 62 | ("pred" | error?="error" "pred"?) |
62 | name=Identifier | 63 | name=Identifier |
63 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | 64 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" |
@@ -88,13 +89,10 @@ RuleDefinition: | |||
88 | "."; | 89 | "."; |
89 | 90 | ||
90 | enum ParameterBinding: | 91 | enum ParameterBinding: |
91 | FOCUS="&" | MULTI="*"; | 92 | FOCUS="+" | MULTI="?"; |
92 | 93 | ||
93 | Parameter: | 94 | Parameter: |
94 | ( | 95 | parameterType=[Relation|QualifiedName]? binding=ParameterBinding? name=Identifier; |
95 | (concreteness=Concreteness? modality=Modality)? | ||
96 | parameterType=[Relation|QualifiedName] | ||
97 | )? binding=ParameterBinding? name=Identifier; | ||
98 | 96 | ||
99 | Consequent: | 97 | Consequent: |
100 | actions+=Action ("," actions+=Action)*; | 98 | actions+=Action ("," actions+=Action)*; |
@@ -183,13 +181,14 @@ AggregationExpr: | |||
183 | "{" value=Expr "|" condition=ComparisonExpr "}"; | 181 | "{" value=Expr "|" condition=ComparisonExpr "}"; |
184 | 182 | ||
185 | enum Concreteness: | 183 | enum Concreteness: |
186 | CANDIDATE="candidate"; | 184 | PARTIAL="partial" | CANDIDATE="candidate"; |
187 | 185 | ||
188 | enum Modality: | 186 | enum Modality: |
189 | MUST="must" | MAY="may"; | 187 | MUST="must" | MAY="may"; |
190 | 188 | ||
191 | ModalExpr: | 189 | ModalExpr: |
192 | concreteness=Concreteness? modality=Modality body=UnaryExpr; | 190 | (concreteness=Concreteness => modality=Modality? | modality=Modality => concreteness=Concreteness?) |
191 | body=UnaryExpr; | ||
193 | 192 | ||
194 | CastExpr returns Expr: | 193 | CastExpr returns Expr: |
195 | CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?; | 194 | CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?; |
@@ -295,7 +294,7 @@ Identifier: | |||
295 | 294 | ||
296 | NonContainmentIdentifier: | 295 | NonContainmentIdentifier: |
297 | ID | "atom" | "multi" | "contained" | "problem" | "module" | | 296 | ID | "atom" | "multi" | "contained" | "problem" | "module" | |
298 | "datatype" | "aggregator" | "decision" | "propagation"; | 297 | "datatype" | "aggregator" | "decision" | "propagation" | "computed"; |
299 | 298 | ||
300 | Real returns ecore::EDouble: | 299 | Real returns ecore::EDouble: |
301 | EXPONENTIAL | INT "." (INT | EXPONENTIAL); | 300 | EXPONENTIAL | INT "." (INT | EXPONENTIAL); |
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 505c7787..8ebd34a5 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 | |||
@@ -46,6 +46,8 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
46 | public static final String MODULE_KIND = DATA_PREFIX + "MODULE_KIND"; | 46 | public static final String MODULE_KIND = DATA_PREFIX + "MODULE_KIND"; |
47 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; | 47 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; |
48 | public static final String COLOR_RELATION_TRUE = "true"; | 48 | public static final String COLOR_RELATION_TRUE = "true"; |
49 | public static final String SHADOW_PREDICATE = DATA_PREFIX + "COMPUTED_VALUE"; | ||
50 | public static final String SHADOW_PREDICATE_TRUE = "true"; | ||
49 | 51 | ||
50 | @Inject | 52 | @Inject |
51 | private IQualifiedNameConverter qualifiedNameConverter; | 53 | private IQualifiedNameConverter qualifiedNameConverter; |
@@ -160,9 +162,12 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
160 | } else if (eObject instanceof AggregatorDeclaration) { | 162 | } else if (eObject instanceof AggregatorDeclaration) { |
161 | builder.put(SHADOWING_KEY, SHADOWING_KEY_AGGREGATOR); | 163 | builder.put(SHADOWING_KEY, SHADOWING_KEY_AGGREGATOR); |
162 | } | 164 | } |
163 | if (eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError()) { | 165 | if (ProblemUtil.isError(eObject)) { |
164 | builder.put(ERROR_PREDICATE, ERROR_PREDICATE_TRUE); | 166 | builder.put(ERROR_PREDICATE, ERROR_PREDICATE_TRUE); |
165 | } | 167 | } |
168 | if (ProblemUtil.isShadow(eObject)) { | ||
169 | builder.put(SHADOW_PREDICATE, SHADOW_PREDICATE_TRUE); | ||
170 | } | ||
166 | var documentationMap = documentationCommentParser.parseDocumentation(eObject); | 171 | var documentationMap = documentationCommentParser.parseDocumentation(eObject); |
167 | builder.putAll(documentationMap); | 172 | builder.putAll(documentationMap); |
168 | return builder.build(); | 173 | return builder.build(); |
@@ -173,7 +178,8 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
173 | return !ProblemUtil.isMultiNode(node); | 178 | return !ProblemUtil.isMultiNode(node); |
174 | } | 179 | } |
175 | if (eObject instanceof PredicateDefinition predicateDefinition) { | 180 | if (eObject instanceof PredicateDefinition predicateDefinition) { |
176 | return !ProblemUtil.isInvalidMultiplicityConstraint(predicateDefinition); | 181 | return !ProblemUtil.isInvalidMultiplicityConstraint(predicateDefinition) && |
182 | !ProblemUtil.isComputedValuePredicate(predicateDefinition); | ||
177 | } | 183 | } |
178 | return true; | 184 | return true; |
179 | } | 185 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java index efa77c50..44f55563 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java | |||
@@ -22,6 +22,7 @@ import tools.refinery.language.utils.ProblemUtil; | |||
22 | 22 | ||
23 | import java.util.*; | 23 | import java.util.*; |
24 | import java.util.function.Function; | 24 | import java.util.function.Function; |
25 | import java.util.function.UnaryOperator; | ||
25 | 26 | ||
26 | @Singleton | 27 | @Singleton |
27 | public class ProblemDerivedStateComputer implements IDerivedStateComputer { | 28 | public class ProblemDerivedStateComputer implements IDerivedStateComputer { |
@@ -59,7 +60,7 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
59 | } | 60 | } |
60 | 61 | ||
61 | protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) { | 62 | protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) { |
62 | installDerivedClassDeclarationState(problem, adapter); | 63 | installDerivedDeclarationState(problem, adapter); |
63 | if (preLinkingPhase) { | 64 | if (preLinkingPhase) { |
64 | return; | 65 | return; |
65 | } | 66 | } |
@@ -67,13 +68,15 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
67 | derivedVariableComputer.installDerivedVariables(problem, nodeNames); | 68 | derivedVariableComputer.installDerivedVariables(problem, nodeNames); |
68 | } | 69 | } |
69 | 70 | ||
70 | protected void installDerivedClassDeclarationState(Problem problem, Adapter adapter) { | 71 | protected void installDerivedDeclarationState(Problem problem, Adapter adapter) { |
71 | for (var statement : problem.getStatements()) { | 72 | for (var statement : problem.getStatements()) { |
72 | if (statement instanceof ClassDeclaration classDeclaration) { | 73 | if (statement instanceof ClassDeclaration classDeclaration) { |
73 | installOrRemoveNewNode(adapter, classDeclaration); | 74 | installOrRemoveNewNode(adapter, classDeclaration); |
74 | for (var referenceDeclaration : classDeclaration.getFeatureDeclarations()) { | 75 | for (var referenceDeclaration : classDeclaration.getFeatureDeclarations()) { |
75 | installOrRemoveInvalidMultiplicityPredicate(adapter, classDeclaration, referenceDeclaration); | 76 | installOrRemoveInvalidMultiplicityPredicate(adapter, classDeclaration, referenceDeclaration); |
76 | } | 77 | } |
78 | } else if (statement instanceof PredicateDefinition predicateDefinition) { | ||
79 | installOrRemoveComputedValuePredicate(adapter, predicateDefinition); | ||
77 | } | 80 | } |
78 | } | 81 | } |
79 | } | 82 | } |
@@ -118,6 +121,27 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
118 | } | 121 | } |
119 | } | 122 | } |
120 | 123 | ||
124 | protected void installOrRemoveComputedValuePredicate(Adapter adapter, PredicateDefinition predicateDefinition) { | ||
125 | if (ProblemUtil.hasComputedValue(predicateDefinition)) { | ||
126 | var computedValue = adapter.createComputedValuePredicateIfAbsent(predicateDefinition, key -> { | ||
127 | var predicate = ProblemFactory.eINSTANCE.createPredicateDefinition(); | ||
128 | predicate.setShadow(true); | ||
129 | predicate.setName("computed"); | ||
130 | return predicate; | ||
131 | }); | ||
132 | var parameters = computedValue.getParameters(); | ||
133 | parameters.clear(); | ||
134 | parameters.addAll(EcoreUtil.copyAll(predicateDefinition.getParameters())); | ||
135 | predicateDefinition.setComputedValue(computedValue); | ||
136 | } else { | ||
137 | var computedValue = predicateDefinition.getComputedValue(); | ||
138 | if (computedValue != null) { | ||
139 | predicateDefinition.setComputedValue(null); | ||
140 | adapter.removeComputedValuePredicate(computedValue); | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
121 | protected Set<String> installDerivedNodes(Problem problem) { | 145 | protected Set<String> installDerivedNodes(Problem problem) { |
122 | var collector = nodeNameCollectorProvider.get(); | 146 | var collector = nodeNameCollectorProvider.get(); |
123 | collector.collectNodeNames(problem); | 147 | collector.collectNodeNames(problem); |
@@ -148,6 +172,7 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
148 | protected void discardDerivedProblemState(Problem problem, Adapter adapter) { | 172 | protected void discardDerivedProblemState(Problem problem, Adapter adapter) { |
149 | var abstractClassDeclarations = new HashSet<ClassDeclaration>(); | 173 | var abstractClassDeclarations = new HashSet<ClassDeclaration>(); |
150 | var referenceDeclarationsWithMultiplicity = new HashSet<ReferenceDeclaration>(); | 174 | var referenceDeclarationsWithMultiplicity = new HashSet<ReferenceDeclaration>(); |
175 | var predicateDefinitionsWithComputedValue = new HashSet<PredicateDefinition>(); | ||
151 | problem.getNodes().clear(); | 176 | problem.getNodes().clear(); |
152 | for (var statement : problem.getStatements()) { | 177 | for (var statement : problem.getStatements()) { |
153 | if (statement instanceof ClassDeclaration classDeclaration) { | 178 | if (statement instanceof ClassDeclaration classDeclaration) { |
@@ -160,9 +185,13 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
160 | referenceDeclarationsWithMultiplicity.add(referenceDeclaration); | 185 | referenceDeclarationsWithMultiplicity.add(referenceDeclaration); |
161 | } | 186 | } |
162 | } | 187 | } |
188 | } else if (statement instanceof PredicateDefinition predicateDefinition && | ||
189 | ProblemUtil.hasComputedValue(predicateDefinition)) { | ||
190 | predicateDefinitionsWithComputedValue.add(predicateDefinition); | ||
163 | } | 191 | } |
164 | } | 192 | } |
165 | adapter.retainAll(abstractClassDeclarations, referenceDeclarationsWithMultiplicity); | 193 | adapter.retainAll(abstractClassDeclarations, referenceDeclarationsWithMultiplicity, |
194 | predicateDefinitionsWithComputedValue); | ||
166 | derivedVariableComputer.discardDerivedVariables(problem); | 195 | derivedVariableComputer.discardDerivedVariables(problem); |
167 | } | 196 | } |
168 | 197 | ||
@@ -185,6 +214,7 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
185 | protected static class Adapter extends AdapterImpl { | 214 | protected static class Adapter extends AdapterImpl { |
186 | private final Map<ClassDeclaration, Node> newNodes = new HashMap<>(); | 215 | private final Map<ClassDeclaration, Node> newNodes = new HashMap<>(); |
187 | private final Map<ReferenceDeclaration, PredicateDefinition> invalidMultiplicityPredicates = new HashMap<>(); | 216 | private final Map<ReferenceDeclaration, PredicateDefinition> invalidMultiplicityPredicates = new HashMap<>(); |
217 | private final Map<PredicateDefinition, PredicateDefinition> computedValuePredicates = new HashMap<>(); | ||
188 | 218 | ||
189 | public Node createNewNodeIfAbsent(ClassDeclaration classDeclaration, | 219 | public Node createNewNodeIfAbsent(ClassDeclaration classDeclaration, |
190 | Function<ClassDeclaration, Node> createNode) { | 220 | Function<ClassDeclaration, Node> createNode) { |
@@ -205,10 +235,21 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer { | |||
205 | invalidMultiplicityPredicates.remove(referenceDeclaration); | 235 | invalidMultiplicityPredicates.remove(referenceDeclaration); |
206 | } | 236 | } |
207 | 237 | ||
238 | public PredicateDefinition createComputedValuePredicateIfAbsent( | ||
239 | PredicateDefinition predicateDefinition, UnaryOperator<PredicateDefinition> createPredicate) { | ||
240 | return computedValuePredicates.computeIfAbsent(predicateDefinition, createPredicate); | ||
241 | } | ||
242 | |||
243 | public void removeComputedValuePredicate(PredicateDefinition predicateDefinition) { | ||
244 | computedValuePredicates.remove(predicateDefinition); | ||
245 | } | ||
246 | |||
208 | public void retainAll(Collection<ClassDeclaration> abstractClassDeclarations, | 247 | public void retainAll(Collection<ClassDeclaration> abstractClassDeclarations, |
209 | Collection<ReferenceDeclaration> referenceDeclarationsWithMultiplicity) { | 248 | Collection<ReferenceDeclaration> referenceDeclarationsWithMultiplicity, |
249 | Collection<PredicateDefinition> predicateDefinitionsWithComputedValue) { | ||
210 | newNodes.keySet().retainAll(abstractClassDeclarations); | 250 | newNodes.keySet().retainAll(abstractClassDeclarations); |
211 | invalidMultiplicityPredicates.keySet().retainAll(referenceDeclarationsWithMultiplicity); | 251 | invalidMultiplicityPredicates.keySet().retainAll(referenceDeclarationsWithMultiplicity); |
252 | computedValuePredicates.keySet().retainAll(predicateDefinitionsWithComputedValue); | ||
212 | } | 253 | } |
213 | 254 | ||
214 | @Override | 255 | @Override |
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 63815506..f1e5a7ac 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 | |||
@@ -75,7 +75,7 @@ public class TypedModule { | |||
75 | private void checkTypes(PredicateDefinition predicateDefinition) { | 75 | private void checkTypes(PredicateDefinition predicateDefinition) { |
76 | for (var conjunction : predicateDefinition.getBodies()) { | 76 | for (var conjunction : predicateDefinition.getBodies()) { |
77 | for (var literal : conjunction.getLiterals()) { | 77 | for (var literal : conjunction.getLiterals()) { |
78 | coerceIntoLiteral(literal); | 78 | coerceIntoLiteral(literal, predicateDefinition.isShadow()); |
79 | } | 79 | } |
80 | } | 80 | } |
81 | } | 81 | } |
@@ -83,7 +83,7 @@ public class TypedModule { | |||
83 | private void checkTypes(RuleDefinition ruleDefinition) { | 83 | private void checkTypes(RuleDefinition ruleDefinition) { |
84 | for (var conjunction : ruleDefinition.getPreconditions()) { | 84 | for (var conjunction : ruleDefinition.getPreconditions()) { |
85 | for (var literal : conjunction.getLiterals()) { | 85 | for (var literal : conjunction.getLiterals()) { |
86 | expectType(literal, ExprType.MODAL_LITERAL); | 86 | coerceIntoLiteral(literal, true); |
87 | } | 87 | } |
88 | } | 88 | } |
89 | for (var consequent : ruleDefinition.getConsequents()) { | 89 | for (var consequent : ruleDefinition.getConsequents()) { |
@@ -393,7 +393,7 @@ public class TypedModule { | |||
393 | 393 | ||
394 | @NotNull | 394 | @NotNull |
395 | private ExprType computeExpressionType(CountExpr countExpr) { | 395 | private ExprType computeExpressionType(CountExpr countExpr) { |
396 | return coerceIntoLiteral(countExpr.getBody()) ? BuiltinTermInterpreter.INT_TYPE : ExprType.INVALID; | 396 | return coerceIntoLiteral(countExpr.getBody(), false) ? BuiltinTermInterpreter.INT_TYPE : ExprType.INVALID; |
397 | } | 397 | } |
398 | 398 | ||
399 | @NotNull | 399 | @NotNull |
@@ -403,7 +403,7 @@ public class TypedModule { | |||
403 | return ExprType.INVALID; | 403 | return ExprType.INVALID; |
404 | } | 404 | } |
405 | // Avoid short-circuiting to let us type check both the value and the condition. | 405 | // Avoid short-circuiting to let us type check both the value and the condition. |
406 | boolean ok = coerceIntoLiteral(expr.getCondition()); | 406 | boolean ok = coerceIntoLiteral(expr.getCondition(), false); |
407 | var value = expr.getValue(); | 407 | var value = expr.getValue(); |
408 | var actualType = getExpressionType(value); | 408 | var actualType = getExpressionType(value); |
409 | if (actualType == ExprType.INVALID) { | 409 | if (actualType == ExprType.INVALID) { |
@@ -617,12 +617,12 @@ public class TypedModule { | |||
617 | } | 617 | } |
618 | } | 618 | } |
619 | 619 | ||
620 | private boolean coerceIntoLiteral(Expr expr) { | 620 | private boolean coerceIntoLiteral(Expr expr, boolean allowModal) { |
621 | if (expr == null) { | 621 | if (expr == null) { |
622 | return false; | 622 | return false; |
623 | } | 623 | } |
624 | var actualType = getExpressionType(expr); | 624 | var actualType = getExpressionType(expr); |
625 | if (actualType == ExprType.LITERAL) { | 625 | if (actualType == ExprType.LITERAL || (allowModal && actualType == ExprType.MODAL_LITERAL)) { |
626 | return true; | 626 | return true; |
627 | } | 627 | } |
628 | return expectType(expr, actualType, BuiltinTermInterpreter.BOOLEAN_TYPE); | 628 | return expectType(expr, actualType, BuiltinTermInterpreter.BOOLEAN_TYPE); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java index 72f23e85..3b2ae707 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java | |||
@@ -12,6 +12,7 @@ public final class BuiltinSymbols { | |||
12 | private final ClassDeclaration node; | 12 | private final ClassDeclaration node; |
13 | private final PredicateDefinition equals; | 13 | private final PredicateDefinition equals; |
14 | private final PredicateDefinition exists; | 14 | private final PredicateDefinition exists; |
15 | private final ClassDeclaration container; | ||
15 | private final ClassDeclaration contained; | 16 | private final ClassDeclaration contained; |
16 | private final PredicateDefinition contains; | 17 | private final PredicateDefinition contains; |
17 | private final PredicateDefinition invalidContainer; | 18 | private final PredicateDefinition invalidContainer; |
@@ -21,6 +22,7 @@ public final class BuiltinSymbols { | |||
21 | node = getDeclaration(ClassDeclaration.class, "node"); | 22 | node = getDeclaration(ClassDeclaration.class, "node"); |
22 | equals = getDeclaration(PredicateDefinition.class, "equals"); | 23 | equals = getDeclaration(PredicateDefinition.class, "equals"); |
23 | exists = getDeclaration(PredicateDefinition.class, "exists"); | 24 | exists = getDeclaration(PredicateDefinition.class, "exists"); |
25 | container = getDeclaration(ClassDeclaration.class, "container"); | ||
24 | contained = getDeclaration(ClassDeclaration.class, "contained"); | 26 | contained = getDeclaration(ClassDeclaration.class, "contained"); |
25 | contains = getDeclaration(PredicateDefinition.class, "contains"); | 27 | contains = getDeclaration(PredicateDefinition.class, "contains"); |
26 | invalidContainer = getDeclaration(PredicateDefinition.class, "invalidContainer"); | 28 | invalidContainer = getDeclaration(PredicateDefinition.class, "invalidContainer"); |
@@ -42,6 +44,10 @@ public final class BuiltinSymbols { | |||
42 | return exists; | 44 | return exists; |
43 | } | 45 | } |
44 | 46 | ||
47 | public ClassDeclaration container() { | ||
48 | return container; | ||
49 | } | ||
50 | |||
45 | public ClassDeclaration contained() { | 51 | public ClassDeclaration contained() { |
46 | return contained; | 52 | return contained; |
47 | } | 53 | } |
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 e1820261..067e684a 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 | |||
@@ -53,6 +53,19 @@ public final class ProblemUtil { | |||
53 | return eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError(); | 53 | return eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError(); |
54 | } | 54 | } |
55 | 55 | ||
56 | public static boolean isShadow(EObject eObject) { | ||
57 | return eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isShadow(); | ||
58 | } | ||
59 | |||
60 | public static boolean mayReferToShadow(EObject context) { | ||
61 | var definitionContext = EcoreUtil2.getContainerOfType(context, ParametricDefinition.class); | ||
62 | return switch (definitionContext) { | ||
63 | case PredicateDefinition predicateDefinition -> predicateDefinition.isShadow(); | ||
64 | case RuleDefinition ignored -> true; | ||
65 | case null, default -> false; | ||
66 | }; | ||
67 | } | ||
68 | |||
56 | public static boolean isAtomNode(Node node) { | 69 | public static boolean isAtomNode(Node node) { |
57 | var containingFeature = node.eContainingFeature(); | 70 | var containingFeature = node.eContainingFeature(); |
58 | if (containingFeature == ProblemPackage.Literals.NODE_DECLARATION__NODES) { | 71 | if (containingFeature == ProblemPackage.Literals.NODE_DECLARATION__NODES) { |
@@ -73,9 +86,12 @@ public final class ProblemUtil { | |||
73 | return node.eContainingFeature() == ProblemPackage.Literals.NODE_DECLARATION__NODES; | 86 | return node.eContainingFeature() == ProblemPackage.Literals.NODE_DECLARATION__NODES; |
74 | } | 87 | } |
75 | 88 | ||
76 | public static boolean isInvalidMultiplicityConstraint(PredicateDefinition predicateDefinition) { | 89 | public static boolean isInvalidMultiplicityConstraint(Relation relation) { |
77 | return predicateDefinition.eContainingFeature() == | 90 | return relation.eContainingFeature() == ProblemPackage.Literals.REFERENCE_DECLARATION__INVALID_MULTIPLICITY; |
78 | ProblemPackage.Literals.REFERENCE_DECLARATION__INVALID_MULTIPLICITY; | 91 | } |
92 | |||
93 | public static boolean isComputedValuePredicate(Relation relation) { | ||
94 | return relation.eContainingFeature() == ProblemPackage.Literals.PREDICATE_DEFINITION__COMPUTED_VALUE; | ||
79 | } | 95 | } |
80 | 96 | ||
81 | public static boolean hasMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) { | 97 | public static boolean hasMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) { |
@@ -96,6 +112,10 @@ public final class ProblemUtil { | |||
96 | return true; | 112 | return true; |
97 | } | 113 | } |
98 | 114 | ||
115 | public static boolean hasComputedValue(PredicateDefinition predicateDefinition) { | ||
116 | return !predicateDefinition.isShadow() && !predicateDefinition.getBodies().isEmpty(); | ||
117 | } | ||
118 | |||
99 | public static boolean isTypeLike(Relation relation) { | 119 | public static boolean isTypeLike(Relation relation) { |
100 | if (relation instanceof ClassDeclaration || relation instanceof EnumDeclaration || | 120 | if (relation instanceof ClassDeclaration || relation instanceof EnumDeclaration || |
101 | relation instanceof DatatypeDeclaration) { | 121 | relation instanceof DatatypeDeclaration) { |
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 deleted file mode 100644 index 5812bdc4..00000000 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ExistsVariableCollector.java +++ /dev/null | |||
@@ -1,75 +0,0 @@ | |||
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 63c8631c..aef1e71d 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 | |||
@@ -49,11 +49,11 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
49 | public static final String INVALID_MODALITY_ISSUE = ISSUE_PREFIX + "INVALID_MODALITY"; | 49 | public static final String INVALID_MODALITY_ISSUE = ISSUE_PREFIX + "INVALID_MODALITY"; |
50 | public static final String INVALID_RULE_ISSUE = ISSUE_PREFIX + "INVALID_RULE"; | 50 | public static final String INVALID_RULE_ISSUE = ISSUE_PREFIX + "INVALID_RULE"; |
51 | 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"; |
52 | public static final String SHADOW_RELATION_ISSUE = ISSUE_PREFIX + "SHADOW_RELATION"; | ||
52 | public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION"; | 53 | public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION"; |
53 | public static final String UNKNOWN_EXPRESSION_ISSUE = ISSUE_PREFIX + "UNKNOWN_EXPRESSION"; | 54 | public static final String UNKNOWN_EXPRESSION_ISSUE = ISSUE_PREFIX + "UNKNOWN_EXPRESSION"; |
54 | public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT"; | 55 | public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT"; |
55 | public static final String TYPE_ERROR = ISSUE_PREFIX + "TYPE_ERROR"; | 56 | 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"; | 57 | public static final String UNUSED_PARTIAL_RELATION = ISSUE_PREFIX + "UNUSED_PARTIAL_RELATION"; |
58 | public static final String UNUSED_PARAMETER = ISSUE_PREFIX + "UNUSED_PARAMETER"; | 58 | public static final String UNUSED_PARAMETER = ISSUE_PREFIX + "UNUSED_PARAMETER"; |
59 | 59 | ||
@@ -61,9 +61,6 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
61 | private ReferenceCounter referenceCounter; | 61 | private ReferenceCounter referenceCounter; |
62 | 62 | ||
63 | @Inject | 63 | @Inject |
64 | private ExistsVariableCollector existsVariableCollector; | ||
65 | |||
66 | @Inject | ||
67 | private ActionTargetCollector actionTargetCollector; | 64 | private ActionTargetCollector actionTargetCollector; |
68 | 65 | ||
69 | @Inject | 66 | @Inject |
@@ -135,14 +132,6 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
135 | var variableOrNode = expr.getVariableOrNode(); | 132 | var variableOrNode = expr.getVariableOrNode(); |
136 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) | 133 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) |
137 | && !ProblemUtil.isSingletonVariable(variable)) { | 134 | && !ProblemUtil.isSingletonVariable(variable)) { |
138 | if (EcoreUtil2.getContainerOfType(variable, ParametricDefinition.class) instanceof RuleDefinition && | ||
139 | EcoreUtil2.getContainerOfType(variable, NegationExpr.class) == null && | ||
140 | // If there is an exists constraint, it is the only constraint. | ||
141 | existsVariableCollector.missingExistsConstraint(variable)) { | ||
142 | // Existentially quantified variables in rules should not be singletons, | ||
143 | // because we have to add an {@code exists} constraint as well. | ||
144 | return; | ||
145 | } | ||
146 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); | 135 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); |
147 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { | 136 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { |
148 | var name = variable.getName(); | 137 | var name = variable.getName(); |
@@ -368,32 +357,48 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
368 | 357 | ||
369 | @Check | 358 | @Check |
370 | public void checkReferenceType(ReferenceDeclaration referenceDeclaration) { | 359 | public void checkReferenceType(ReferenceDeclaration referenceDeclaration) { |
360 | var referenceType = referenceDeclaration.getReferenceType(); | ||
361 | if (referenceType == null || referenceType.eIsProxy()) { | ||
362 | return; | ||
363 | } | ||
371 | boolean isDefaultReference = referenceDeclaration.getKind() == ReferenceKind.DEFAULT && | 364 | boolean isDefaultReference = referenceDeclaration.getKind() == ReferenceKind.DEFAULT && |
372 | !ProblemUtil.isContainerReference(referenceDeclaration); | 365 | !ProblemUtil.isContainerReference(referenceDeclaration); |
373 | if (isDefaultReference || referenceDeclaration.getKind() == ReferenceKind.REFERENCE) { | 366 | if (isDefaultReference || referenceDeclaration.getKind() == ReferenceKind.REFERENCE) { |
374 | checkArity(referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__REFERENCE_TYPE, 1); | 367 | checkArity(referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__REFERENCE_TYPE, 1); |
375 | return; | 368 | if (ProblemUtil.isShadow(referenceType)) { |
369 | var message = "Shadow relations '%s' is not allowed in reference types." | ||
370 | .formatted(referenceType.getName()); | ||
371 | acceptError(message, referenceDeclaration, | ||
372 | ProblemPackage.Literals.REFERENCE_DECLARATION__REFERENCE_TYPE, 0, SHADOW_RELATION_ISSUE); | ||
373 | } | ||
374 | } else if (!(referenceType instanceof ClassDeclaration)) { | ||
375 | var message = "Reference type '%s' of the containment or container reference '%s' is not a class." | ||
376 | .formatted(referenceType.getName(), referenceDeclaration.getName()); | ||
377 | acceptError(message, referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__REFERENCE_TYPE, | ||
378 | 0, INVALID_REFERENCE_TYPE_ISSUE); | ||
376 | } | 379 | } |
377 | var referenceType = referenceDeclaration.getReferenceType(); | 380 | } |
378 | if (referenceType == null || referenceType.eIsProxy() || referenceType instanceof ClassDeclaration) { | 381 | |
379 | // Either correct, or a missing reference type where we are probably already emitting another error. | 382 | @Check |
380 | return; | 383 | public void checkPredicateDefinition(PredicateDefinition predicateDefinition) { |
384 | if (predicateDefinition.isError() && predicateDefinition.isShadow()) { | ||
385 | var message = "Shadow predicates cannot be marked as error predicates."; | ||
386 | acceptError(message, predicateDefinition, ProblemPackage.Literals.PREDICATE_DEFINITION__ERROR, 0, | ||
387 | SHADOW_RELATION_ISSUE); | ||
381 | } | 388 | } |
382 | var message = "Reference type '%s' of the containment or container reference '%s' is not a class." | ||
383 | .formatted(referenceType.getName(), referenceDeclaration.getName()); | ||
384 | acceptError(message, referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__REFERENCE_TYPE, 0, | ||
385 | INVALID_REFERENCE_TYPE_ISSUE); | ||
386 | } | 389 | } |
387 | 390 | ||
388 | @Check | 391 | @Check |
389 | public void checkParameter(Parameter parameter) { | 392 | public void checkParameter(Parameter parameter) { |
390 | checkArity(parameter, ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 1); | 393 | checkArity(parameter, ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 1); |
394 | var type = parameter.getParameterType(); | ||
395 | if (type != null && !type.eIsProxy() && ProblemUtil.isShadow(type)) { | ||
396 | var message = "Shadow relations '%s' is not allowed in parameter types.".formatted(type.getName()); | ||
397 | acceptError(message, parameter, ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 0, | ||
398 | SHADOW_RELATION_ISSUE); | ||
399 | } | ||
391 | var parametricDefinition = EcoreUtil2.getContainerOfType(parameter, ParametricDefinition.class); | 400 | var parametricDefinition = EcoreUtil2.getContainerOfType(parameter, ParametricDefinition.class); |
392 | if (parametricDefinition instanceof RuleDefinition rule) { | 401 | if (parametricDefinition instanceof RuleDefinition rule) { |
393 | if (parameter.getParameterType() != null && parameter.getModality() == Modality.NONE) { | ||
394 | acceptError("Parameter type modality must be specified.", parameter, | ||
395 | ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE, 0, INVALID_MODALITY_ISSUE); | ||
396 | } | ||
397 | var kind = rule.getKind(); | 402 | var kind = rule.getKind(); |
398 | var binding = parameter.getBinding(); | 403 | var binding = parameter.getBinding(); |
399 | if (kind == RuleKind.PROPAGATION && binding != ParameterBinding.SINGLE) { | 404 | if (kind == RuleKind.PROPAGATION && binding != ParameterBinding.SINGLE) { |
@@ -404,10 +409,6 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
404 | ProblemPackage.Literals.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); | 409 | ProblemPackage.Literals.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); |
405 | } | 410 | } |
406 | } else { | 411 | } else { |
407 | if (parameter.getConcreteness() != Concreteness.PARTIAL || parameter.getModality() != Modality.NONE) { | ||
408 | acceptError("Modal parameter types are only supported in rule definitions.", parameter, null, 0, | ||
409 | INVALID_MODALITY_ISSUE); | ||
410 | } | ||
411 | if (parameter.getBinding() != ParameterBinding.SINGLE) { | 412 | if (parameter.getBinding() != ParameterBinding.SINGLE) { |
412 | acceptError("Parameter binding annotations are only supported in decision rules.", parameter, | 413 | acceptError("Parameter binding annotations are only supported in decision rules.", parameter, |
413 | ProblemPackage.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); | 414 | ProblemPackage.PARAMETER__BINDING, 0, INVALID_MODALITY_ISSUE); |
@@ -418,13 +419,21 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
418 | @Check | 419 | @Check |
419 | public void checkAtom(Atom atom) { | 420 | public void checkAtom(Atom atom) { |
420 | int argumentCount = atom.getArguments().size(); | 421 | int argumentCount = atom.getArguments().size(); |
421 | checkArity(atom, ProblemPackage.Literals.ATOM__RELATION, argumentCount); | ||
422 | if (atom.isTransitiveClosure() && argumentCount != 2) { | 422 | if (atom.isTransitiveClosure() && argumentCount != 2) { |
423 | var message = "Transitive closure needs exactly 2 arguments, got %d arguments instead." | 423 | var message = "Transitive closure needs exactly 2 arguments, got %d arguments instead." |
424 | .formatted(argumentCount); | 424 | .formatted(argumentCount); |
425 | acceptError(message, atom, ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE, 0, | 425 | acceptError(message, atom, ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE, 0, |
426 | INVALID_TRANSITIVE_CLOSURE_ISSUE); | 426 | INVALID_TRANSITIVE_CLOSURE_ISSUE); |
427 | } | 427 | } |
428 | var target = atom.getRelation(); | ||
429 | if (target == null || target.eIsProxy()) { | ||
430 | return; | ||
431 | } | ||
432 | checkArity(atom, ProblemPackage.Literals.ATOM__RELATION, argumentCount); | ||
433 | if (ProblemUtil.isShadow(target) && !ProblemUtil.mayReferToShadow(atom)) { | ||
434 | var message = "Shadow relation '%s' is not allowed in a non-shadow context.".formatted(target.getName()); | ||
435 | acceptError(message, atom, ProblemPackage.Literals.ATOM__RELATION, 0, SHADOW_RELATION_ISSUE); | ||
436 | } | ||
428 | } | 437 | } |
429 | 438 | ||
430 | @Check | 439 | @Check |
@@ -433,28 +442,6 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
433 | acceptError("Rules must have exactly one consequent.", ruleDefinition, | 442 | acceptError("Rules must have exactly one consequent.", ruleDefinition, |
434 | ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, INVALID_RULE_ISSUE); | 443 | ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, INVALID_RULE_ISSUE); |
435 | } | 444 | } |
436 | var unquantifiedVariables = new HashSet<Variable>(); | ||
437 | for (var variable : EcoreUtil2.getAllContentsOfType(ruleDefinition, Variable.class)) { | ||
438 | if (existsVariableCollector.missingExistsConstraint(variable)) { | ||
439 | unquantifiedVariables.add(variable); | ||
440 | } | ||
441 | } | ||
442 | for (var expr : EcoreUtil2.getAllContentsOfType(ruleDefinition, VariableOrNodeExpr.class)) { | ||
443 | if (expr.getVariableOrNode() instanceof Variable variable && unquantifiedVariables.contains(variable)) { | ||
444 | unquantifiedVariables.remove(variable); | ||
445 | var name = variable.getName(); | ||
446 | String message; | ||
447 | if (ProblemUtil.isSingletonVariable(variable)) { | ||
448 | message = ("Remove the singleton variable marker '_' and clarify the quantification of variable " + | ||
449 | "'%s'.").formatted(name); | ||
450 | } else { | ||
451 | message = ("Add a 'must exists(%s)', 'may exists(%s)', or 'may !exists(%s)' constraint to " + | ||
452 | "clarify the quantification of variable '%s'.").formatted(name, name, name, name); | ||
453 | } | ||
454 | acceptWarning(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, 0, | ||
455 | VARIABLE_WITHOUT_EXISTS); | ||
456 | } | ||
457 | } | ||
458 | } | 445 | } |
459 | 446 | ||
460 | @Check | 447 | @Check |
@@ -467,33 +454,17 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
467 | for (var consequent : consequents) { | 454 | for (var consequent : consequents) { |
468 | countRuleParameterUsages(consequent, useCounts); | 455 | countRuleParameterUsages(consequent, useCounts); |
469 | } | 456 | } |
470 | var isError = ruleDefinition.getKind() == RuleKind.PROPAGATION; | ||
471 | int consequentCount = consequents.size(); | 457 | int consequentCount = consequents.size(); |
472 | for (var entry : useCounts.entrySet()) { | 458 | for (var entry : useCounts.entrySet()) { |
473 | if (entry.getValue() < consequentCount) { | 459 | if (entry.getValue() < consequentCount) { |
474 | var parameter = entry.getKey(); | 460 | var parameter = entry.getKey(); |
475 | var message = "Unused rule parameter '%s'.".formatted(parameter.getName()); | 461 | var message = "Unused rule parameter '%s'.".formatted(parameter.getName()); |
476 | if (isError) { | 462 | acceptWarning(message, parameter, ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, |
477 | acceptError(message, parameter, ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, UNUSED_PARAMETER); | 463 | UNUSED_PARAMETER); |
478 | } else { | ||
479 | acceptWarning(message, parameter, ProblemPackage.Literals.NAMED_ELEMENT__NAME, 0, | ||
480 | UNUSED_PARAMETER); | ||
481 | } | ||
482 | } | 464 | } |
483 | } | 465 | } |
484 | } | 466 | } |
485 | 467 | ||
486 | @Check | ||
487 | public void checkPropagationRuleConsequent(Consequent consequent) { | ||
488 | var rule = EcoreUtil2.getContainerOfType(consequent, RuleDefinition.class); | ||
489 | if (rule == null || rule.getKind() != RuleKind.PROPAGATION) { | ||
490 | return; | ||
491 | } | ||
492 | if (consequent.getActions().size() > 1) { | ||
493 | acceptError("Propagation rules must have exactly one action.", consequent, null, 0, INVALID_RULE_ISSUE); | ||
494 | } | ||
495 | } | ||
496 | |||
497 | private static void countRuleParameterUsages(Consequent consequent, Map<Parameter, Integer> useCounts) { | 468 | private static void countRuleParameterUsages(Consequent consequent, Map<Parameter, Integer> useCounts) { |
498 | var usedParameters = new HashSet<Parameter>(); | 469 | var usedParameters = new HashSet<Parameter>(); |
499 | for (var action : consequent.getActions()) { | 470 | for (var action : consequent.getActions()) { |
@@ -502,14 +473,15 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
502 | } | 473 | } |
503 | } | 474 | } |
504 | for (var usedParameter : usedParameters) { | 475 | for (var usedParameter : usedParameters) { |
505 | useCounts.compute(usedParameter, (ignored, value) -> value == null ? 0 : value + 1); | 476 | useCounts.compute(usedParameter, (ignored, value) -> value == null ? null : value + 1); |
506 | } | 477 | } |
507 | } | 478 | } |
508 | 479 | ||
509 | private static void collectUsedParameters(AssertionAction assertionAction, HashSet<Parameter> usedParameters) { | 480 | private static void collectUsedParameters(AssertionAction assertionAction, HashSet<Parameter> usedParameters) { |
510 | for (var argument : assertionAction.getArguments()) { | 481 | for (var argument : assertionAction.getArguments()) { |
511 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument && | 482 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument && |
512 | nodeAssertionArgument.getNode() instanceof Parameter usedParameter) { | 483 | nodeAssertionArgument.getNode() instanceof Parameter usedParameter && |
484 | !usedParameter.eIsProxy()) { | ||
513 | usedParameters.add(usedParameter); | 485 | usedParameters.add(usedParameter); |
514 | } | 486 | } |
515 | } | 487 | } |
@@ -518,23 +490,40 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
518 | @Check | 490 | @Check |
519 | public void checkAssertion(AbstractAssertion assertion) { | 491 | public void checkAssertion(AbstractAssertion assertion) { |
520 | var relation = assertion.getRelation(); | 492 | var relation = assertion.getRelation(); |
493 | if (relation == null || relation.eIsProxy()) { | ||
494 | return; | ||
495 | } | ||
521 | if (relation instanceof DatatypeDeclaration) { | 496 | if (relation instanceof DatatypeDeclaration) { |
522 | var message = "Assertions for data types are not supported."; | 497 | var message = "Assertions for data types are not supported."; |
523 | acceptError(message, assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, 0, | 498 | acceptError(message, assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, 0, |
524 | UNSUPPORTED_ASSERTION_ISSUE); | 499 | UNSUPPORTED_ASSERTION_ISSUE); |
525 | return; | 500 | return; |
526 | } | 501 | } |
502 | if (ProblemUtil.isShadow(relation)) { | ||
503 | var message = "Shadow relation '%s' may not have any assertions.".formatted(relation.getName()); | ||
504 | acceptError(message, assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, 0, | ||
505 | SHADOW_RELATION_ISSUE); | ||
506 | } | ||
527 | int argumentCount = assertion.getArguments().size(); | 507 | int argumentCount = assertion.getArguments().size(); |
528 | checkArity(assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, argumentCount); | 508 | checkArity(assertion, ProblemPackage.Literals.ABSTRACT_ASSERTION__RELATION, argumentCount); |
529 | } | 509 | } |
530 | 510 | ||
531 | @Check | 511 | @Check |
532 | public void checkTypeScope(TypeScope typeScope) { | 512 | public void checkTypeScope(TypeScope typeScope) { |
533 | checkArity(typeScope, ProblemPackage.Literals.TYPE_SCOPE__TARGET_TYPE, 1); | ||
534 | if (typeScope.isIncrement() && ProblemUtil.isInModule(typeScope)) { | 513 | if (typeScope.isIncrement() && ProblemUtil.isInModule(typeScope)) { |
535 | acceptError("Incremental type scopes are not supported in modules", typeScope, null, 0, | 514 | acceptError("Incremental type scopes are not supported in modules", typeScope, null, 0, |
536 | INVALID_MULTIPLICITY_ISSUE); | 515 | INVALID_MULTIPLICITY_ISSUE); |
537 | } | 516 | } |
517 | var type = typeScope.getTargetType(); | ||
518 | if (type == null || type.eIsProxy()) { | ||
519 | return; | ||
520 | } | ||
521 | checkArity(typeScope, ProblemPackage.Literals.TYPE_SCOPE__TARGET_TYPE, 1); | ||
522 | if (ProblemUtil.isShadow(type)) { | ||
523 | var message = "Shadow relations '%s' is not allowed in type scopes.".formatted(type.getName()); | ||
524 | acceptError(message, typeScope, ProblemPackage.Literals.TYPE_SCOPE__TARGET_TYPE, 0, | ||
525 | SHADOW_RELATION_ISSUE); | ||
526 | } | ||
538 | } | 527 | } |
539 | 528 | ||
540 | private void checkArity(EObject eObject, EReference reference, int expectedArity) { | 529 | private void checkArity(EObject eObject, EReference reference, int expectedArity) { |
diff --git a/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery index 09c7d92b..6ae6beb1 100644 --- a/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery +++ b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery | |||
@@ -8,6 +8,8 @@ pred exists(node). | |||
8 | 8 | ||
9 | pred equals(left, right). | 9 | pred equals(left, right). |
10 | 10 | ||
11 | abstract class container extends node. | ||
12 | |||
11 | abstract class contained extends node. | 13 | abstract class contained extends node. |
12 | 14 | ||
13 | pred contains(container, contained contained). | 15 | pred contains(container, contained contained). |