Skip to content

Commit 2f3791e

Browse files
committed
[1617] Incorrect resolution of redefined attribute during import
Bug: #1617 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
1 parent 29e89b0 commit 2f3791e

4 files changed

Lines changed: 198 additions & 4 deletions

File tree

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
- https://github.com/eclipse-syson/syson/issues/1581[#1581] [diagrams] Remove rename and delete tools from inherited port palette.
2424
- https://github.com/eclipse-syson/syson/issues/1611[#1611] [services] Fix an issue where the undo of the deletion of a graphical node in a diagram was not restoring the graphical node correctly.
2525
- https://github.com/eclipse-syson/syson/issues/1618[#1618] [rest-apis] Fix an issue where the _eAnnotations_ reference coming from the Ecore metamodel was taking into account by REST APIs serialization.
26+
- https://github.com/eclipse-syson/syson/issues/1617[#1617] [import] Fix incorrect resolution of _redefined feature_ on `Redefinitions` during textual import.
27+
2628

2729
=== Improvements
2830

backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,64 @@ public void tearDown() {
149149
TestTransaction.start();
150150
}
151151

152+
@Test
153+
@DisplayName("Given of model with redefinition depending on inherited memberships computation, WHEN importing the model, THEN redefined feature should resolve properly using inherited memberships")
154+
public void checkRedefinedFeatureToInheritedFields() throws IOException {
155+
var input = """
156+
private import ISQBase::mass;
157+
#MP part p2 {
158+
attribute :>> mass = 2.0;
159+
}
160+
private import Metaobjects::SemanticMetadata;
161+
part p {
162+
attribute mass;
163+
}
164+
metadata def MP :> SemanticMetadata {
165+
:>> baseType = p meta SysML::Systems::PartUsage;
166+
:> annotatedElement : SysML::Systems::PartUsage;
167+
}
168+
part p3 :> p1::p2::p0 {
169+
attribute :>> mass = 2.0;
170+
}
171+
package p1 {
172+
package p2 {
173+
part p0 :> p;
174+
}
175+
}
176+
part p {
177+
attribute mass;
178+
}
179+
""";
180+
181+
this.checker.checkImportedModel(resource -> {
182+
Optional<AttributeUsage> pMass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
183+
.filter(partUsage -> "p::mass".equals(partUsage.getQualifiedName()))
184+
.findFirst();
185+
186+
assertThat(pMass)
187+
.isPresent();
188+
189+
// Checks that p2::mass redefined p::mass and not ISQBase::mass because of the use of the semantic MetadataDefinition that implicitly makes p2 subset p
190+
Optional<AttributeUsage> p2Mass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
191+
.filter(partUsage -> "p2::mass".equals(partUsage.getQualifiedName()))
192+
.findFirst();
193+
assertThat(p2Mass)
194+
.isPresent();
195+
196+
assertThat(p2Mass.get()).matches(attributeUsage -> attributeUsage.redefines(pMass.get()));
197+
198+
// Checks that p2::mass redefined p::mass and not ISQBase::mass because p3 subsets p1::p2::p0 and p0 subsets p
199+
Optional<AttributeUsage> p3Mass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
200+
.filter(partUsage -> "p3::mass".equals(partUsage.getQualifiedName()))
201+
.findFirst();
202+
assertThat(p2Mass)
203+
.isPresent();
204+
205+
assertThat(p2Mass.get()).matches(attributeUsage -> attributeUsage.redefines(pMass.get()));
206+
207+
}).check(input);
208+
}
209+
152210
@Test
153211
@DisplayName("GIVEN a model with a FlowUsage using a FeatureChain, WHEN importing the model, THEN the source and the target of the FlowUsage should be properly resolved")
154212
public void checkFlowUsageWithFeatureChain() throws IOException {

backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/parser/AstTreeParser.java

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
import java.util.Comparator;
2020
import java.util.Iterator;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.Map.Entry;
24+
import java.util.stream.Collectors;
2325
import java.util.stream.StreamSupport;
2426

2527
import org.eclipse.emf.ecore.EObject;
2628
import org.eclipse.emf.ecore.EReference;
29+
import org.eclipse.emf.ecore.InternalEObject;
2730
import org.eclipse.syson.sysml.Element;
31+
import org.eclipse.syson.sysml.Import;
32+
import org.eclipse.syson.sysml.Redefinition;
33+
import org.eclipse.syson.sysml.Specialization;
2834
import org.eclipse.syson.sysml.helper.EMFUtils;
2935
import org.eclipse.syson.sysml.parser.translation.EClassifierTranslator;
3036
import org.eclipse.syson.sysml.utils.LogNameProvider;
@@ -157,6 +163,86 @@ private void handleNodeValue(final EObject eObject, String key, JsonNode value)
157163
}
158164

159165
public void resolveAllReference(List<ProxiedReference> unresolvedReferences) {
166+
/*
167+
* We need to order some of the proxy to resolve. For example, redefinition resolution should be handled once all specializations have been handled.
168+
* Indeed, the name resolution of a redefined feature on Redefinition can depend on :
169+
* <ul>
170+
* <li>The subclassification or subsetting of it owning Namespace (that might be implied through the use of semantic MetadataUsage)</li>
171+
* <li>The type of the owning namespace</li>
172+
* </ul>
173+
*/
174+
Map<ReferenceType, List<ProxiedReference>> partitionedUnresolvedReferences = unresolvedReferences.stream().collect(Collectors.groupingBy(this::getProxyClassification));
175+
176+
// First resolve imports
177+
List<ProxiedReference> unresolvedProxiesAfterImport = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.IMPORT,
178+
"Import resolution phase", List.of());
179+
180+
// Then resolve unclassified proxies and the previous unresolved proxies
181+
List<ProxiedReference> unresolvedProxiesAfterUnqualified = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.UNCLASSIFIED,
182+
"Non qualified proxy resolution phase", unresolvedProxiesAfterImport);
183+
184+
// Then resolve specializations that are not redefinitions since redefinition needs to access inherited memberships
185+
List<ProxiedReference> unresolvedAfterSpecialization = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.OTHER_SPECIALIZATION,
186+
"Non redefinition specialization proxy resolution phase",
187+
unresolvedProxiesAfterUnqualified);
188+
189+
// Then resolve base_type redefinition proxies stored MetadataDefinition since they may impact implicit inherited members
190+
List<ProxiedReference> unresolvedAfterBaseTypeRedefinition = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.BASE_TYPE_METADATA_DEFINITION_REDEFINITION,
191+
"Semantic MetadataData base_type redefinition proxy resolution phase", unresolvedAfterSpecialization);
192+
193+
// At the end resolve redefinition
194+
List<ProxiedReference> unresolvedAfterRedefinition = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.OTHER_REDEFINITION, "Other redefinition proxy resolution phase",
195+
unresolvedAfterBaseTypeRedefinition);
196+
197+
if (!unresolvedAfterRedefinition.isEmpty()) {
198+
this.handleUnresolvableProxies(unresolvedAfterRedefinition);
199+
}
200+
}
201+
202+
203+
private ReferenceType getProxyClassification(ProxiedReference proxyReference) {
204+
EObject owner = proxyReference.owner();
205+
InternalEObject targetProxy = proxyReference.targetProxy();
206+
final ReferenceType result;
207+
if (owner instanceof Redefinition && targetProxy.toString().contains("baseType")) {
208+
result = ReferenceType.BASE_TYPE_METADATA_DEFINITION_REDEFINITION;
209+
} else if (owner instanceof Import) {
210+
result = ReferenceType.IMPORT;
211+
} else if (owner instanceof Redefinition) {
212+
result = ReferenceType.OTHER_REDEFINITION;
213+
} else if (owner instanceof Specialization) {
214+
result = ReferenceType.OTHER_SPECIALIZATION;
215+
} else {
216+
result = ReferenceType.UNCLASSIFIED;
217+
}
218+
return result;
219+
}
220+
221+
/**
222+
* Runs a resolution phase of a given class of proxy
223+
*
224+
* @param classifiedProxies
225+
* all the proxy classified
226+
* @param proxyClass
227+
* the type of proxy to resolve
228+
* @param resolutionPhaseName
229+
* a name of resolution phase
230+
* @param yetNotResolved
231+
* A list of unresolved proxy from the previous phases
232+
* @return all proxies that have not been resolved
233+
*/
234+
private List<ProxiedReference> doResolveAllReference(Map<ReferenceType, List<ProxiedReference>> classifiedProxies, ReferenceType proxyClass, String resolutionPhaseName,
235+
List<ProxiedReference> yetNotResolved) {
236+
List<ProxiedReference> toResolve = classifiedProxies.get(proxyClass);
237+
if (toResolve != null && !toResolve.isEmpty()) {
238+
List<ProxiedReference> tmpToResolve = new ArrayList<>(toResolve);
239+
tmpToResolve.addAll(yetNotResolved);
240+
return this.doResolveAllReference(tmpToResolve, resolutionPhaseName);
241+
}
242+
return yetNotResolved;
243+
}
244+
245+
private List<ProxiedReference> doResolveAllReference(List<ProxiedReference> unresolvedReferences, String resolutionPhase) {
160246
// Sorts proxies to resolve by breadth-first search strategy. This way we resolve first the import
161247
// generally located at top level part of the model. Those proxies are often used by lowest part of the
162248
// model to resolve their link
@@ -172,18 +258,19 @@ public void resolveAllReference(List<ProxiedReference> unresolvedReferences) {
172258
List<ProxiedReference> proxiedReferences = unresolvedReferencesTmp.stream()
173259
.filter(proxiedReference -> !this.proxyResolver.resolveProxy(proxiedReference))
174260
.toList();
175-
LOGGER.info(MessageFormat.format("{0} remaining proxies to resolve after {1} try", Integer.toString(proxiedReferences.size()), Integer.toString(tryNb)));
261+
LOGGER.info(MessageFormat.format("{2} : {0} remaining proxies to resolve after {1} try", Integer.toString(proxiedReferences.size()), Integer.toString(tryNb), resolutionPhase));
176262
if (proxiedReferences.equals(unresolvedReferencesTmp)) {
177-
this.printResolutionError(proxiedReferences);
178263
break;
179264
} else {
180265
unresolvedReferencesTmp = proxiedReferences;
181266
}
182267
}
268+
269+
return unresolvedReferencesTmp;
183270
}
184271

185-
private void printResolutionError(List<ProxiedReference> proxiedReferences) {
186-
for (ProxiedReference pr : proxiedReferences) {
272+
private void handleUnresolvableProxies(List<ProxiedReference> unresolvableProxies) {
273+
for (ProxiedReference pr : unresolvableProxies) {
187274
String msg = MessageFormat.format("Unable to resolve name ''{1}'' for reference ''{2}'' on element ''{0}''", this.logNameProvider.getName(pr.owner()),
188275
pr.targetProxy().eProxyURI().fragment(),
189276
pr.reference().getName());
@@ -233,4 +320,30 @@ public int compare(EObject owner1, EObject owner2) {
233320
return signum;
234321
}
235322
}
323+
324+
/**
325+
* Classification of proxy to resolve.
326+
*/
327+
private enum ReferenceType {
328+
/**
329+
* A proxy related to imports.
330+
*/
331+
IMPORT,
332+
/**
333+
* All proxies that do not belong to any other categories.
334+
*/
335+
UNCLASSIFIED,
336+
/**
337+
* Proxy of the "base_type" redefinition feature of the "SemanticMetadata" used in MetadataDefinition.
338+
*/
339+
BASE_TYPE_METADATA_DEFINITION_REDEFINITION,
340+
/**
341+
* All other redefinitions except the one stored in BASE_TYPE_METADATA_DEFINITION_REDEFINITION.
342+
*/
343+
OTHER_REDEFINITION,
344+
/**
345+
* All specializations except the Redefinition.
346+
*/
347+
OTHER_SPECIALIZATION
348+
}
236349
}

doc/content/modules/user-manual/pages/release-notes/2025.12.0.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ requirement <'SN'> {
4040

4141
- Fix an issue where the undo of the deletion of a graphical node in a diagram was not restoring the graphical node correctly.
4242
- Fix an issue where the _eAnnotations_ reference coming from the Ecore metamodel was taking into account by REST APIs serialization.
43+
- Fix invalid resolution during import of _redefined Feature_ on `Redefinition`.
44+
This problem appears in cases of a name conflict between a global available item and an item accessible via _inherited membership_ such as in the following model.
45+
46+
```
47+
private import ISQBase::mass; // Gives global access to "mass"
48+
49+
#MP part p2 { // Implicit subsetting "p" using a semantic MetadataDefinition
50+
attribute :>> mass = 2.0; // Should redefine "p::mass" and not "ISQBase::mass"
51+
}
52+
53+
private import Metaobjects::SemanticMetadata;
54+
55+
part p {
56+
attribute mass;
57+
}
58+
59+
metadata def MP :> SemanticMetadata {
60+
:>> baseType = p meta SysML::PartUsage;
61+
:> annotatedElement : SysML::PartUsage;
62+
}
63+
```
4364

4465
== Improvements
4566

0 commit comments

Comments
 (0)