aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-18 20:44:00 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-18 20:44:00 +0100
commit6c4d81ba085d24035ad1f2e45d4e731d3b33e6db (patch)
tree1b4d29eb5a06b25199836468c7b8ccb55b49afd8
parentfeat(language): filter content assist for imports (diff)
downloadrefinery-6c4d81ba085d24035ad1f2e45d4e731d3b33e6db.tar.gz
refinery-6c4d81ba085d24035ad1f2e45d4e731d3b33e6db.tar.zst
refinery-6c4d81ba085d24035ad1f2e45d4e731d3b33e6db.zip
refactor(language): no fully qualified self import
Make sure it is impossible to create clashing fully qualified names when renaming a module by forbidding modules from referring to their own elements with fully qualified names. Therefore, serializing a solution will not create clashing fully qualified names (which would prevent serialization from succeeding).
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java41
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java106
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java62
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java29
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java25
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java70
6 files changed, 202 insertions, 131 deletions
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
index 110295b2..9c40e6df 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -8,11 +8,14 @@ package tools.refinery.language.semantics;
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Singleton; 9import com.google.inject.Singleton;
10import com.google.inject.name.Named; 10import com.google.inject.name.Named;
11import org.eclipse.emf.ecore.EClass;
11import org.eclipse.emf.ecore.EObject; 12import org.eclipse.emf.ecore.EObject;
12import org.eclipse.emf.ecore.util.EcoreUtil; 13import org.eclipse.emf.ecore.util.EcoreUtil;
13import org.eclipse.xtext.naming.IQualifiedNameConverter; 14import org.eclipse.xtext.naming.IQualifiedNameConverter;
14import org.eclipse.xtext.naming.IQualifiedNameProvider; 15import org.eclipse.xtext.naming.IQualifiedNameProvider;
15import org.eclipse.xtext.naming.QualifiedName; 16import org.eclipse.xtext.naming.QualifiedName;
17import org.eclipse.xtext.resource.IEObjectDescription;
18import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
16import org.eclipse.xtext.scoping.IScope; 19import org.eclipse.xtext.scoping.IScope;
17import org.jetbrains.annotations.NotNull; 20import org.jetbrains.annotations.NotNull;
18import org.jetbrains.annotations.Nullable; 21import org.jetbrains.annotations.Nullable;
@@ -33,6 +36,9 @@ public class SemanticsUtils {
33 @Inject 36 @Inject
34 private IQualifiedNameConverter qualifiedNameConverter; 37 private IQualifiedNameConverter qualifiedNameConverter;
35 38
39 @Inject
40 private IResourceDescriptionsProvider resourceDescriptionsProvider;
41
36 public Optional<String> getNameWithoutRootPrefix(EObject eObject) { 42 public Optional<String> getNameWithoutRootPrefix(EObject eObject) {
37 var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(eObject); 43 var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(eObject);
38 if (qualifiedName == null) { 44 if (qualifiedName == null) {
@@ -42,11 +48,31 @@ public class SemanticsUtils {
42 } 48 }
43 49
44 @Nullable 50 @Nullable
51 public <T> T maybeGetLocalElement(Problem problem, QualifiedName qualifiedName, Class<T> type, EClass eClass) {
52 var resource = problem.eResource();
53 var resourceSet = resource.getResourceSet();
54 var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resourceSet);
55 var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI());
56 if (resourceDescription == null) {
57 return null;
58 }
59 var eObjectDescriptions = resourceDescription.getExportedObjects(eClass, qualifiedName, false);
60 return maybeGet(problem, eObjectDescriptions, qualifiedName, type);
61 }
62
63 @Nullable
45 public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { 64 public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) {
46 if (qualifiedName == null) { 65 if (qualifiedName == null) {
47 throw new IllegalArgumentException("Element name must not be null"); 66 throw new IllegalArgumentException("Element name must not be null");
48 } 67 }
49 var iterator = scope.getElements(qualifiedName).iterator(); 68 var eObjectDescriptions = scope.getElements(qualifiedName);
69 return maybeGet(problem, eObjectDescriptions, qualifiedName, type);
70 }
71
72 @Nullable
73 private <T> T maybeGet(Problem problem, Iterable<IEObjectDescription> eObjectDescriptions,
74 QualifiedName qualifiedName, Class<T> type) {
75 var iterator = eObjectDescriptions.iterator();
50 if (!iterator.hasNext()) { 76 if (!iterator.hasNext()) {
51 return null; 77 return null;
52 } 78 }
@@ -66,8 +92,19 @@ public class SemanticsUtils {
66 } 92 }
67 93
68 @NotNull 94 @NotNull
95 public <T> T getLocalElement(Problem problem, QualifiedName qualifiedName, Class<T> type, EClass eClass) {
96 var element = maybeGetLocalElement(problem, qualifiedName, type, eClass);
97 return getOrThrow(element, qualifiedName, type);
98 }
99
100 @NotNull
69 public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { 101 public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) {
70 var element = maybeGetElement(problem, scope, qualifiedName, type); 102 var element = maybeGetElement(problem, scope, qualifiedName, type);
103 return getOrThrow(element, qualifiedName, type);
104 }
105
106 @NotNull
107 private <T> T getOrThrow(@Nullable T element, QualifiedName qualifiedName, Class<T> type) {
71 if (element == null) { 108 if (element == null) {
72 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); 109 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
73 throw new IllegalArgumentException("No such %s: %s" 110 throw new IllegalArgumentException("No such %s: %s"
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
index 2fb0a49d..377a66f3 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
@@ -10,6 +10,8 @@ import com.google.inject.Provider;
10import org.eclipse.collections.api.factory.primitive.IntObjectMaps; 10import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
11import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; 11import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
12import org.eclipse.emf.common.util.URI; 12import org.eclipse.emf.common.util.URI;
13import org.eclipse.emf.ecore.EObject;
14import org.eclipse.emf.ecore.resource.Resource;
13import org.eclipse.emf.ecore.util.EcoreUtil; 15import org.eclipse.emf.ecore.util.EcoreUtil;
14import org.eclipse.xtext.naming.IQualifiedNameProvider; 16import org.eclipse.xtext.naming.IQualifiedNameProvider;
15import org.eclipse.xtext.naming.QualifiedName; 17import org.eclipse.xtext.naming.QualifiedName;
@@ -18,7 +20,7 @@ import org.eclipse.xtext.resource.XtextResource;
18import org.eclipse.xtext.resource.XtextResourceSet; 20import org.eclipse.xtext.resource.XtextResourceSet;
19import org.eclipse.xtext.scoping.IScopeProvider; 21import org.eclipse.xtext.scoping.IScopeProvider;
20import tools.refinery.language.model.problem.*; 22import tools.refinery.language.model.problem.*;
21import tools.refinery.language.scoping.imports.ImportAdapter; 23import tools.refinery.language.naming.NamingUtil;
22import tools.refinery.language.utils.ProblemDesugarer; 24import tools.refinery.language.utils.ProblemDesugarer;
23import tools.refinery.language.utils.ProblemUtil; 25import tools.refinery.language.utils.ProblemUtil;
24import tools.refinery.store.model.Model; 26import tools.refinery.store.model.Model;
@@ -34,9 +36,7 @@ import tools.refinery.store.tuple.Tuple;
34import java.io.ByteArrayInputStream; 36import java.io.ByteArrayInputStream;
35import java.io.ByteArrayOutputStream; 37import java.io.ByteArrayOutputStream;
36import java.io.IOException; 38import java.io.IOException;
37import java.util.Map; 39import java.util.*;
38import java.util.TreeMap;
39import java.util.TreeSet;
40import java.util.function.Function; 40import java.util.function.Function;
41import java.util.stream.Collectors; 41import java.util.stream.Collectors;
42 42
@@ -66,13 +66,17 @@ public class SolutionSerializer {
66 private Model model; 66 private Model model;
67 private ReasoningAdapter reasoningAdapter; 67 private ReasoningAdapter reasoningAdapter;
68 private PartialInterpretation<TruthValue, Boolean> existsInterpretation; 68 private PartialInterpretation<TruthValue, Boolean> existsInterpretation;
69 private Resource originalResource;
69 private Problem originalProblem; 70 private Problem originalProblem;
71 private QualifiedName originalProblemName;
70 private Problem problem; 72 private Problem problem;
73 private QualifiedName newProblemName;
71 private NodeDeclaration nodeDeclaration; 74 private NodeDeclaration nodeDeclaration;
72 private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty(); 75 private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty();
73 76
74 public Problem serializeSolution(ProblemTrace trace, Model model) { 77 public Problem serializeSolution(ProblemTrace trace, Model model) {
75 var uri = URI.createURI("__synthetic." + ProblemUtil.MODULE_EXTENSION); 78 var uri = URI.createURI("__solution_%s.%s".formatted(UUID.randomUUID().toString().replace('-', '_'),
79 ProblemUtil.MODULE_EXTENSION));
76 return serializeSolution(trace, model, uri); 80 return serializeSolution(trace, model, uri);
77 } 81 }
78 82
@@ -83,10 +87,14 @@ public class SolutionSerializer {
83 existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, 87 existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE,
84 ReasoningAdapter.EXISTS_SYMBOL); 88 ReasoningAdapter.EXISTS_SYMBOL);
85 originalProblem = trace.getProblem(); 89 originalProblem = trace.getProblem();
90 originalProblemName = qualifiedNameProvider.getFullyQualifiedName(originalProblem);
86 problem = copyProblem(originalProblem, uri); 91 problem = copyProblem(originalProblem, uri);
87 problem.setKind(ModuleKind.MODULE); 92 problem.setKind(ProblemUtil.getDefaultModuleKind(uri));
93 problem.setExplicitKind(false);
94 problem.setName(null);
95 newProblemName = qualifiedNameProvider.getFullyQualifiedName(originalProblem);
88 problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); 96 problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement);
89 problem.getNodes().removeIf(this::shouldRemoveNode); 97 removeNonExistentImplicitNodes();
90 nodeDeclaration = ProblemFactory.eINSTANCE.createNodeDeclaration(); 98 nodeDeclaration = ProblemFactory.eINSTANCE.createNodeDeclaration();
91 nodeDeclaration.setKind(NodeKind.NODE); 99 nodeDeclaration.setKind(NodeKind.NODE);
92 nodeDeclaration.getNodes().addAll(problem.getNodes()); 100 nodeDeclaration.getNodes().addAll(problem.getNodes());
@@ -105,15 +113,28 @@ public class SolutionSerializer {
105 return statement instanceof Assertion || statement instanceof ScopeDeclaration; 113 return statement instanceof Assertion || statement instanceof ScopeDeclaration;
106 } 114 }
107 115
108 private boolean shouldRemoveNode(Node newNode) { 116 private void removeNonExistentImplicitNodes() {
109 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(newNode); 117 var originalImplicitNodes = originalProblem.getNodes();
110 var scope = scopeProvider.getScope(originalProblem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); 118 var newImplicitNodes = problem.getNodes();
111 var originalNode = semanticsUtils.maybeGetElement(originalProblem, scope, qualifiedName, Node.class); 119 if (newImplicitNodes.size() != originalImplicitNodes.size()) {
112 if (originalNode == null) { 120 throw new IllegalStateException("Expected %d implicit nodes in problem, but got %d after copying"
113 return false; 121 .formatted(originalImplicitNodes.size(), newImplicitNodes.size()));
122 }
123 var iterator = newImplicitNodes.iterator();
124 for (var originalNode : originalImplicitNodes) {
125 if (!iterator.hasNext()) {
126 throw new AssertionError("Unexpected end of copied implicit node list");
127 }
128 var newNode = iterator.next();
129 if (!Objects.equals(originalNode.getName(), newNode.getName())) {
130 throw new IllegalStateException("Expected copy of '%s' to have the same name, got '%s' instead"
131 .formatted(originalNode.getName(), newNode.getName()));
132 }
133 int nodeId = trace.getNodeId(originalNode);
134 if (!isExistingNode(nodeId)) {
135 iterator.remove();
136 }
114 } 137 }
115 int nodeId = trace.getNodeId(originalNode);
116 return !isExistingNode(nodeId);
117 } 138 }
118 139
119 private boolean isExistingNode(int nodeId) { 140 private boolean isExistingNode(int nodeId) {
@@ -125,14 +146,10 @@ public class SolutionSerializer {
125 } 146 }
126 147
127 private Problem copyProblem(Problem originalProblem, URI uri) { 148 private Problem copyProblem(Problem originalProblem, URI uri) {
128 var newResourceSet = resourceSetProvider.get(); 149 originalResource = originalProblem.eResource();
129 ImportAdapter.copySettings(originalProblem, newResourceSet); 150 var resourceSet = originalResource.getResourceSet();
130 if (!ProblemUtil.MODULE_EXTENSION.equals(uri.fileExtension())) {
131 uri = uri.appendFileExtension(ProblemUtil.MODULE_EXTENSION);
132 }
133 var newResource = resourceFactory.createResource(uri); 151 var newResource = resourceFactory.createResource(uri);
134 newResourceSet.getResources().add(newResource); 152 resourceSet.getResources().add(newResource);
135 var originalResource = originalProblem.eResource();
136 if (originalResource instanceof XtextResource) { 153 if (originalResource instanceof XtextResource) {
137 byte[] bytes; 154 byte[] bytes;
138 try { 155 try {
@@ -147,7 +164,7 @@ public class SolutionSerializer {
147 throw new IllegalStateException("Failed to copy problem", e); 164 throw new IllegalStateException("Failed to copy problem", e);
148 } 165 }
149 var contents = newResource.getContents(); 166 var contents = newResource.getContents();
150 EcoreUtil.resolveAll(newResourceSet); 167 EcoreUtil.resolveAll(newResource);
151 if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) { 168 if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) {
152 return newProblem; 169 return newProblem;
153 } 170 }
@@ -159,10 +176,24 @@ public class SolutionSerializer {
159 } 176 }
160 } 177 }
161 178
179 private QualifiedName getConvertedName(EObject original) {
180 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(original);
181 if (originalProblemName != null && qualifiedName.startsWith(originalProblemName)) {
182 qualifiedName = qualifiedName.skipFirst(originalProblemName.getSegmentCount());
183 }
184 if (newProblemName != null) {
185 qualifiedName = newProblemName.append(qualifiedName);
186 }
187 return NamingUtil.addRootPrefix(qualifiedName);
188 }
189
162 private Relation findRelation(Relation originalRelation) { 190 private Relation findRelation(Relation originalRelation) {
163 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalRelation); 191 if (originalRelation.eResource() != originalResource) {
164 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); 192 return originalRelation;
165 return semanticsUtils.getElement(problem, scope, qualifiedName, Relation.class); 193 }
194 var qualifiedName = getConvertedName(originalRelation);
195 return semanticsUtils.getLocalElement(problem, qualifiedName, Relation.class,
196 ProblemPackage.Literals.RELATION);
166 } 197 }
167 198
168 private Relation findPartialRelation(PartialRelation partialRelation) { 199 private Relation findPartialRelation(PartialRelation partialRelation) {
@@ -170,13 +201,11 @@ public class SolutionSerializer {
170 } 201 }
171 202
172 private Node findNode(Node originalNode) { 203 private Node findNode(Node originalNode) {
173 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalNode); 204 if (originalNode.eResource() != originalResource) {
174 return findNode(qualifiedName); 205 return originalNode;
175 } 206 }
176 207 var qualifiedName = getConvertedName(originalNode);
177 private Node findNode(QualifiedName qualifiedName) { 208 return semanticsUtils.maybeGetLocalElement(problem, qualifiedName, Node.class, ProblemPackage.Literals.NODE);
178 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
179 return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class);
180 } 209 }
181 210
182 private void addAssertion(Relation relation, LogicValue value, Node... arguments) { 211 private void addAssertion(Relation relation, LogicValue value, Node... arguments) {
@@ -194,8 +223,8 @@ public class SolutionSerializer {
194 } 223 }
195 224
196 private void addExistsAssertions() { 225 private void addExistsAssertions() {
197 var builtinSymbols = desugarer.getBuiltinSymbols(problem) 226 var builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalStateException("No " +
198 .orElseThrow(() -> new IllegalStateException("No builtin library in copied problem")); 227 "builtin library in copied problem"));
199 // Make sure to output exists assertions in a deterministic order. 228 // Make sure to output exists assertions in a deterministic order.
200 var sortedNewNodes = new TreeMap<Integer, Node>(); 229 var sortedNewNodes = new TreeMap<Integer, Node>();
201 for (var pair : trace.getNodeTrace().keyValuesView()) { 230 for (var pair : trace.getNodeTrace().keyValuesView()) {
@@ -204,8 +233,7 @@ public class SolutionSerializer {
204 var newNode = findNode(originalNode); 233 var newNode = findNode(originalNode);
205 // Since all implicit nodes that do not exist has already been removed in serializeSolution, 234 // Since all implicit nodes that do not exist has already been removed in serializeSolution,
206 // we only need to add !exists assertions to ::new nodes and explicitly declared nodes that do not exist. 235 // we only need to add !exists assertions to ::new nodes and explicitly declared nodes that do not exist.
207 if (ProblemUtil.isMultiNode(originalNode) || 236 if (ProblemUtil.isMultiNode(originalNode) || (ProblemUtil.isDeclaredNode(originalNode) && !isExistingNode(nodeId))) {
208 (ProblemUtil.isDeclaredNode(originalNode) && !isExistingNode(nodeId))) {
209 sortedNewNodes.put(nodeId, newNode); 237 sortedNewNodes.put(nodeId, newNode);
210 } else { 238 } else {
211 nodes.put(nodeId, newNode); 239 nodes.put(nodeId, newNode);
@@ -218,8 +246,8 @@ public class SolutionSerializer {
218 } 246 }
219 247
220 private void addClassAssertions() { 248 private void addClassAssertions() {
221 var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() 249 var types =
222 .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); 250 trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream().collect(Collectors.toMap(Function.identity(), this::findPartialRelation));
223 var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); 251 var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll();
224 while (cursor.move()) { 252 while (cursor.move()) {
225 var key = cursor.getKey(); 253 var key = cursor.getKey();
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java
new file mode 100644
index 00000000..f9405fc1
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping;
7
8import com.google.common.base.Predicate;
9import com.google.common.collect.Iterables;
10import org.eclipse.emf.ecore.EClass;
11import org.eclipse.emf.ecore.EObject;
12import org.eclipse.xtext.naming.QualifiedName;
13import org.eclipse.xtext.resource.IEObjectDescription;
14import org.eclipse.xtext.resource.ISelectable;
15import tools.refinery.language.naming.NamingUtil;
16
17import java.util.List;
18
19public class NoFullyQualifiedNamesSelectable implements ISelectable {
20 private final ISelectable delegateSelectable;
21
22 // {@link com.google.common.base.Predicate} required by Xtext API.
23 @SuppressWarnings("squid:S4738")
24 private final Predicate<IEObjectDescription> filter =
25 eObjectDescription -> !NamingUtil.isFullyQualified(eObjectDescription.getName());
26
27 public NoFullyQualifiedNamesSelectable(ISelectable delegateSelectable) {
28 this.delegateSelectable = delegateSelectable;
29 }
30
31 @Override
32 public boolean isEmpty() {
33 return delegateSelectable.isEmpty();
34 }
35
36 @Override
37 public Iterable<IEObjectDescription> getExportedObjects() {
38 return filter(delegateSelectable.getExportedObjects());
39 }
40
41 @Override
42 public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) {
43 if (NamingUtil.isFullyQualified(name)) {
44 return List.of();
45 }
46 return delegateSelectable.getExportedObjects(type, name, ignoreCase);
47 }
48
49 @Override
50 public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) {
51 return filter(delegateSelectable.getExportedObjectsByType(type));
52 }
53
54 @Override
55 public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) {
56 return filter(delegateSelectable.getExportedObjectsByObject(object));
57 }
58
59 private Iterable<IEObjectDescription> filter(Iterable<IEObjectDescription> eObjectDescriptions) {
60 return Iterables.filter(eObjectDescriptions, filter);
61 }
62}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
index 1c0c1d86..0067bf94 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
@@ -12,7 +12,6 @@ import org.eclipse.emf.ecore.EReference;
12import org.eclipse.emf.ecore.resource.Resource; 12import org.eclipse.emf.ecore.resource.Resource;
13import org.eclipse.xtext.naming.IQualifiedNameProvider; 13import org.eclipse.xtext.naming.IQualifiedNameProvider;
14import org.eclipse.xtext.naming.QualifiedName; 14import org.eclipse.xtext.naming.QualifiedName;
15import org.eclipse.xtext.resource.IResourceDescription;
16import org.eclipse.xtext.resource.IResourceDescriptionsProvider; 15import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
17import org.eclipse.xtext.resource.ISelectable; 16import org.eclipse.xtext.resource.ISelectable;
18import org.eclipse.xtext.scoping.IScope; 17import org.eclipse.xtext.scoping.IScope;
@@ -39,41 +38,31 @@ public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScop
39 if (resource == null) { 38 if (resource == null) {
40 return IScope.NULLSCOPE; 39 return IScope.NULLSCOPE;
41 } 40 }
41 var globalScope = getGlobalScope(resource, reference);
42 var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); 42 var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource));
43 if (localImports.resourceDescription() == null) { 43 if (localImports == null) {
44 return IScope.NULLSCOPE; 44 return globalScope;
45 } 45 }
46 var globalScope = getGlobalScope(resource, reference);
47 var type = reference.getEReferenceType(); 46 var type = reference.getEReferenceType();
48 boolean ignoreCase = isIgnoreCase(reference); 47 boolean ignoreCase = isIgnoreCase(reference);
49 var scope = ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports.resourceDescription(), 48 return ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports, type, ignoreCase);
50 type, ignoreCase);
51 if (localImports.normalizedSelectable() == null) {
52 return scope;
53 }
54 return ShadowingKeyAwareSelectableBasedScope.createScope(scope, localImports.normalizedSelectable(), type,
55 ignoreCase);
56 } 49 }
57 50
58 protected LocalImports computeLocalImports(Resource resource) { 51 protected ISelectable computeLocalImports(Resource resource) {
59 // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. 52 // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects.
60 var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); 53 var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet());
61 var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); 54 var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI());
62 if (resourceDescription == null) { 55 if (resourceDescription == null) {
63 return new LocalImports(null, null); 56 return null;
64 } 57 }
65 var rootElement = resource.getContents().getFirst(); 58 var rootElement = resource.getContents().getFirst();
66 if (rootElement == null) { 59 if (rootElement == null) {
67 return new LocalImports(resourceDescription, null); 60 return new NoFullyQualifiedNamesSelectable(resourceDescription);
68 } 61 }
69 var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); 62 var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement);
70 if (rootName == null) { 63 if (rootName == null) {
71 return new LocalImports(resourceDescription, null); 64 return new NoFullyQualifiedNamesSelectable(resourceDescription);
72 } 65 }
73 var normalizedSelectable = new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); 66 return new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY);
74 return new LocalImports(resourceDescription, normalizedSelectable);
75 }
76
77 protected record LocalImports(IResourceDescription resourceDescription, ISelectable normalizedSelectable) {
78 } 67 }
79} 68}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
index 5a8f7fd7..d7a5304f 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
@@ -12,7 +12,6 @@ import org.apache.log4j.Logger;
12import org.eclipse.emf.common.notify.Notification; 12import org.eclipse.emf.common.notify.Notification;
13import org.eclipse.emf.common.notify.impl.AdapterImpl; 13import org.eclipse.emf.common.notify.impl.AdapterImpl;
14import org.eclipse.emf.common.util.URI; 14import org.eclipse.emf.common.util.URI;
15import org.eclipse.emf.ecore.EObject;
16import org.eclipse.emf.ecore.resource.Resource; 15import org.eclipse.emf.ecore.resource.Resource;
17import org.eclipse.emf.ecore.resource.ResourceSet; 16import org.eclipse.emf.ecore.resource.ResourceSet;
18import org.eclipse.emf.ecore.util.EcoreUtil; 17import org.eclipse.emf.ecore.util.EcoreUtil;
@@ -203,28 +202,4 @@ public class ImportAdapter extends AdapterImpl {
203 private static ImportAdapter getAdapter(ResourceSet resourceSet) { 202 private static ImportAdapter getAdapter(ResourceSet resourceSet) {
204 return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); 203 return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class);
205 } 204 }
206
207 public static void copySettings(EObject context, ResourceSet newResourceSet) {
208 var resource = context.eResource();
209 if (resource == null) {
210 return;
211 }
212 var originalResourceSet = resource.getResourceSet();
213 if (originalResourceSet == null) {
214 return;
215 }
216 copySettings(originalResourceSet, newResourceSet);
217 }
218
219 public static void copySettings(ResourceSet originalResourceSet, ResourceSet newResourceSet) {
220 var originalAdapter = getAdapter(originalResourceSet);
221 if (originalAdapter == null) {
222 return;
223 }
224 var newAdapter = getOrInstall(newResourceSet);
225 newAdapter.libraries.clear();
226 newAdapter.libraries.addAll(originalAdapter.libraries);
227 newAdapter.libraryPaths.clear();
228 newAdapter.libraryPaths.addAll(originalAdapter.libraryPaths);
229 }
230} 205}
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java
index bc0320a6..0704e026 100644
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java
+++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -68,15 +68,14 @@ class NodeScopingTest {
68 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b"))); 68 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b")));
69 } 69 }
70 70
71 @ParameterizedTest 71 @Test
72 @MethodSource("atomNodeReferenceSource") 72 void atomNodeInAssertionTest() {
73 void atomNodeInAssertionTest(String qualifiedNamePrefix, boolean namedProblem) {
74 var problem = parse(""" 73 var problem = parse("""
75 atom a, b. 74 atom a, b.
76 pred predicate(node x, node y) <-> node(x). 75 pred predicate(node x, node y) <-> node(x).
77 predicate({PARAM}a, {PARAM}a). 76 predicate(a, a).
78 ?predicate({PARAM}a, {PARAM}b). 77 ?predicate(a, b).
79 """, qualifiedNamePrefix, namedProblem); 78 """);
80 assertThat(problem.getResourceErrors(), empty()); 79 assertThat(problem.getResourceErrors(), empty());
81 assertThat(problem.nodeNames(), empty()); 80 assertThat(problem.nodeNames(), empty());
82 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a"))); 81 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a")));
@@ -85,22 +84,17 @@ class NodeScopingTest {
85 assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b"))); 84 assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b")));
86 } 85 }
87 86
88 @ParameterizedTest 87 @Test
89 @MethodSource("atomNodeReferenceSource") 88 void atomNodeInPredicateTest() {
90 void atomNodeInPredicateTest(String qualifiedNamePrefix, boolean namedProblem) {
91 var problem = parse(""" 89 var problem = parse("""
92 atom b. 90 atom b.
93 pred predicate(node a) <-> node({PARAM}b). 91 pred predicate(node a) <-> node(b).
94 """, qualifiedNamePrefix, namedProblem); 92 """);
95 assertThat(problem.getResourceErrors(), empty()); 93 assertThat(problem.getResourceErrors(), empty());
96 assertThat(problem.nodeNames(), empty()); 94 assertThat(problem.nodeNames(), empty());
97 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b"))); 95 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b")));
98 } 96 }
99 97
100 static Stream<Arguments> atomNodeReferenceSource() {
101 return Stream.of(Arguments.of("", false), Arguments.of("", true), Arguments.of("test::", true));
102 }
103
104 @Disabled("No nodes are present in builtin.problem currently") 98 @Disabled("No nodes are present in builtin.problem currently")
105 @ParameterizedTest 99 @ParameterizedTest
106 @MethodSource("builtInNodeReferencesSource") 100 @MethodSource("builtInNodeReferencesSource")
@@ -131,37 +125,30 @@ class NodeScopingTest {
131 return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new")); 125 return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new"));
132 } 126 }
133 127
134 @ParameterizedTest 128 @Test
135 @MethodSource("classNewNodeReferencesSource") 129 void classNewNodeTest() {
136 void classNewNodeTest(String qualifiedName, boolean namedProblem) {
137 var problem = parse(""" 130 var problem = parse("""
138 class Foo. 131 class Foo.
139 pred predicate(node x) <-> node(x). 132 pred predicate(node x) <-> node(x).
140 predicate({PARAM}). 133 predicate(Foo::new).
141 """, qualifiedName, namedProblem); 134 """);
142 assertThat(problem.getResourceErrors(), empty()); 135 assertThat(problem.getResourceErrors(), empty());
143 assertThat(problem.nodeNames(), empty()); 136 assertThat(problem.nodeNames(), empty());
144 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode())); 137 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode()));
145 } 138 }
146 139
147 @ParameterizedTest 140 @Test
148 @MethodSource("classNewNodeReferencesSource") 141 void classNewNodeInPredicateTest() {
149 void classNewNodeInPredicateTest(String qualifiedName, boolean namedProblem) {
150 var problem = parse(""" 142 var problem = parse("""
151 class Foo. 143 class Foo.
152 pred predicate(node x) <-> node({PARAM}). 144 pred predicate(node x) <-> node(Foo::new).
153 """, qualifiedName, namedProblem); 145 """);
154 assertThat(problem.getResourceErrors(), empty()); 146 assertThat(problem.getResourceErrors(), empty());
155 assertThat(problem.nodeNames(), empty()); 147 assertThat(problem.nodeNames(), empty());
156 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), 148 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(),
157 equalTo(problem.findClass("Foo").get().getNewNode())); 149 equalTo(problem.findClass("Foo").get().getNewNode()));
158 } 150 }
159 151
160 static Stream<Arguments> classNewNodeReferencesSource() {
161 return Stream.of(Arguments.of("Foo::new", false), Arguments.of("Foo::new", true),
162 Arguments.of("test::Foo::new", true));
163 }
164
165 @Test 152 @Test
166 void newNodeIsNotSpecial() { 153 void newNodeIsNotSpecial() {
167 var problem = parse(""" 154 var problem = parse("""
@@ -176,12 +163,12 @@ class NodeScopingTest {
176 163
177 @ParameterizedTest 164 @ParameterizedTest
178 @MethodSource("enumLiteralReferencesSource") 165 @MethodSource("enumLiteralReferencesSource")
179 void enumLiteralTest(String qualifiedName, boolean namedProblem) { 166 void enumLiteralTest(String qualifiedName) {
180 var problem = parse(""" 167 var problem = parse("""
181 enum Foo { alpha, beta } 168 enum Foo { alpha, beta }
182 pred predicate(Foo a) <-> node(a). 169 pred predicate(Foo a) <-> node(a).
183 predicate({PARAM}). 170 predicate({PARAM}).
184 """, qualifiedName, namedProblem); 171 """, qualifiedName);
185 assertThat(problem.getResourceErrors(), empty()); 172 assertThat(problem.getResourceErrors(), empty());
186 assertThat(problem.nodeNames(), empty()); 173 assertThat(problem.nodeNames(), empty());
187 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha"))); 174 assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha")));
@@ -189,11 +176,11 @@ class NodeScopingTest {
189 176
190 @ParameterizedTest 177 @ParameterizedTest
191 @MethodSource("enumLiteralReferencesSource") 178 @MethodSource("enumLiteralReferencesSource")
192 void enumLiteralInPredicateTest(String qualifiedName, boolean namedProblem) { 179 void enumLiteralInPredicateTest(String qualifiedName) {
193 var problem = parse(""" 180 var problem = parse("""
194 enum Foo { alpha, beta } 181 enum Foo { alpha, beta }
195 pred predicate(Foo a) <-> node({PARAM}). 182 pred predicate(Foo a) <-> node({PARAM}).
196 """, qualifiedName, namedProblem); 183 """, qualifiedName);
197 assertThat(problem.getResourceErrors(), empty()); 184 assertThat(problem.getResourceErrors(), empty());
198 assertThat(problem.nodeNames(), empty()); 185 assertThat(problem.nodeNames(), empty());
199 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), 186 assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(),
@@ -201,9 +188,7 @@ class NodeScopingTest {
201 } 188 }
202 189
203 static Stream<Arguments> enumLiteralReferencesSource() { 190 static Stream<Arguments> enumLiteralReferencesSource() {
204 return Stream.of(Arguments.of("alpha", false), Arguments.of("alpha", true), Arguments.of("Foo::alpha", false), 191 return Stream.of(Arguments.of("alpha"), Arguments.of("Foo::alpha"));
205 Arguments.of("Foo::alpha", true), Arguments.of("test::alpha", true),
206 Arguments.of("test::Foo::alpha", true));
207 } 192 }
208 193
209 @Disabled("No enum literals are present in builtin.problem currently") 194 @Disabled("No enum literals are present in builtin.problem currently")
@@ -222,7 +207,7 @@ class NodeScopingTest {
222 @Disabled("No enum literals are present in builtin.problem currently") 207 @Disabled("No enum literals are present in builtin.problem currently")
223 @ParameterizedTest 208 @ParameterizedTest
224 @MethodSource("builtInEnumLiteralReferencesSource") 209 @MethodSource("builtInEnumLiteralReferencesSource")
225 void bultInEnumLiteralInPredicateTest(String qualifiedName) { 210 void builtInEnumLiteralInPredicateTest(String qualifiedName) {
226 var problem = parse(""" 211 var problem = parse("""
227 pred predicate() <-> node({PARAM}). 212 pred predicate() <-> node({PARAM}).
228 """, qualifiedName); 213 """, qualifiedName);
@@ -237,13 +222,8 @@ class NodeScopingTest {
237 Arguments.of("builtin::bool::true")); 222 Arguments.of("builtin::bool::true"));
238 } 223 }
239 224
240 private WrappedProblem parse(String text, String parameter, boolean namedProblem) {
241 var problemName = namedProblem ? "problem test.\n" : "";
242 return parseHelper.parse(problemName + text.replace("{PARAM}", parameter));
243 }
244
245 private WrappedProblem parse(String text, String parameter) { 225 private WrappedProblem parse(String text, String parameter) {
246 return parse(text, parameter, false); 226 return parseHelper.parse(text.replace("{PARAM}", parameter));
247 } 227 }
248 228
249 private WrappedProblem parse(String text) { 229 private WrappedProblem parse(String text) {