dangling reference with models from different files [message #1620562] |
Tue, 17 February 2015 04:17  |
Eclipse User |
|
|
|
hello.
with the following grammar
Model:
classes+=Class*
;
Class:
'class' name=ID '{'
variables += Variable*
statements += Statement*
'}';
Variable:
name=ID ':' type=Type ';'
;
Type:
PrimitiveType | ClassType
;
PrimitiveType:
type=PrimitiveTypes
;
ClassType:
refClass = [Class]
;
enum PrimitiveTypes:
BOOL | INT | REAL
;
Statement:
lValue=LValue '=' rValue=RValue ';'
;
LValue:
VariableRef
;
RValue:
VariableRef | ConstantLiteral
;
VariableRef:
variable=[Variable] ({VariableRef.qualifier=current} '.' variable=[Variable])*
;
ConstantLiteral:
value=INT
;
and the following scope provider
override getScope(EObject context, EReference reference) {
if (reference == MyDslPackage.eINSTANCE.variableRef_Variable) {
if (context instanceof VariableRef) {
val type = context.qualifier?.variable?.type
if (type instanceof ClassType) {
return Scopes.scopeFor(type.refClass.variables)
}
}
}
return super.getScope(context, reference)
}
i have a project with two files
file1 with
class A {
x: INT;
y: INT;
}
class B {
vc: C;
}
class D {
vb: B;
vb.vc.va.x = 2;
}
and file2 with
if i edit the line vb.vc.va.x = 2; in file1 (e.g. delete x, activate content assist and choose y) i get a dangling reference error.
this happens because the file1 is reparsed and new model objects are instantiated. on the other hand file2 isn't reparsed and the model object keeps a reference to the old model objects from file1. How can I force file2 to be reparsed as well? The DefaultResourceDescriptionManager.isAffected is not called when editing and even after saving it isn't called with file2 as candidate.
I was able to solve the problem by adding the following code to the scope provider but I'm not sure if this is the way to go.
override getScope(EObject context, EReference reference) {
if (reference == MyDslPackage.eINSTANCE.variableRef_Variable) {
if (context instanceof VariableRef) {
val type = context.qualifier?.variable?.type
if (type instanceof ClassType) {
var class = type.refClass
if (class.eResource == null) {
val uriFragment = EcoreUtil.getURI(class).fragment
class = context.eResource.getEObject(uriFragment) as org.xtext.example.mydsl.myDsl.Class
}
return Scopes.scopeFor(class.variables)
}
}
}
return super.getScope(context, reference)
}
How would you solve the problem?
Thanks in advance!
|
|
|
|
|
Re: dangling reference with models from different files [message #1707339 is a reply to message #1707287] |
Thu, 03 September 2015 16:09   |
Eclipse User |
|
|
|
Dear Christian,
I had read many of your posts over the year. Your articles help me a lot. Thank you.
The problem I faced is a different one. It only happen when we have 'Entity' defined across several files. Let's extend your "Xtext and Dot/Path-Expressions" post to repeat this issue.
First, let's try to add a custom ResourceDescriptionStrategy to export 'Entity' to index.
package org.xtext.example.mydsl.scoping
import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy
import com.google.inject.Singleton
import com.google.inject.Inject
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.xtext.example.mydsl.myDsl.Entity
import org.eclipse.xtext.resource.EObjectDescription
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.util.IAcceptor
import org.eclipse.xtext.resource.IEObjectDescription
import static extension org.eclipse.xtext.EcoreUtil2.*
import org.xtext.example.mydsl.myDsl.Model
@Singleton
class myDslResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy{
@Inject extension IQualifiedNameProvider
override createEObjectDescriptions(EObject eObj, IAcceptor<IEObjectDescription> acceptor){
if(eObj instanceof Model){
(eObj as Model).getAllContentsOfType(typeof(Entity)).forEach[it|
acceptor.accept(EObjectDescription::create((it as Entity).fullyQualifiedName, (it as Entity)))
]
true
} else false
}
}
Then, let's improve the MyDslScopeProvider to access the global index.
def IScope scope_EntityRef_entity(EntityRef entity, EReference ref) {
val model = entity.getContainerOfType(typeof(Model)) as Model
val outerScope=delegateGetScope(model, ref)
val currentScope=Scopes::scopeFor(model.getAllContentsOfType(typeof(Entity)), outerScope)
return currentScope
}
def IScope scope_Reference_type(Reference context, EReference ref) {
val model = context.getContainerOfType(typeof(Model)) as Model
val outerScope=delegateGetScope(context.getContainerOfType(typeof(Model)), ref)
val currentScope=Scopes::scopeFor(context.getAllContentsOfType(typeof(Entity)), outerScope)
return currentScope
}
And don't forget to change the MyDslRuntimeModule.
org.xtext.example.mydsl.AbstractMyDslRuntimeModule {
public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() {
return myDslResourceDescriptionStrategy.class;
}
}
----
Then, let's separate the test code into two separate files:
1. test1.mydsl
entity A {
attr a1 : int
attr a2 : string
ref b : B
ref c : C
}
entity C {
attr c1 : string
attr c2 : int
}
2. test2.mydsl
entity B {
attr b1 : string
attr b2 : string
ref a : A
ref c : C
}
use A.b.b2
use A.b.c.c1
use A.a1
use A.b.a.a1
Now, you can see in the editor of the second test file, where the b2 of A.b.b2; c of A.b.c.c1, the a of A.b.a showing dangling reference error. This is due to when we update test2.mydsl in the editor, the eResource of test2.mydsl is reparsed, which make all references in test1.mydsl back to Entity B in test1.mydsl out-of-date.
Obviously my previous workaround cannot be put inside the scope-provider; otherwise it will result in a endless job.
Any suggestions on these?
Yu-Jiu
|
|
|
|
|
|
Re: dangling reference with models from different files [message #1707446 is a reply to message #1707346] |
Fri, 04 September 2015 14:30   |
Eclipse User |
|
|
|
Thank you Christian, the codes you provide really works!
Overriding the default DefaultResourceDescriptionManager by implementing the isAffectedByAny(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) changes the default builder behavior to reflect the resources that are affected by the current file in edit!
I try to go through some related codes, and I found that the default method called by builder:
isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) in the DefaultResourceDescriptionManager shares the same input parameters as the method:
isAffectedByAny() of IResourceDescription.Manager.AllChangeAware
So, I try to track the execution of the default isAffected() method:
@Override
public boolean isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) {
Set<URI> outgoingReferences = descriptionUtils.collectOutgoingReferences(candidate);
if (!outgoingReferences.isEmpty()) {
for (IResourceDescription.Delta delta : deltas)
if (hasChanges(delta, candidate) && outgoingReferences.contains(delta.getUri()))
return true;
}
// this is a tradeoff - we could either check whether a given delta uri is contained
// in a reachable container and check for intersecting names afterwards, or we can do
// the other way round
// unfortunately there is no way to decide reliably which algorithm scales better
// note that this method is called for each description so we have something like a
// number of deltas x number of resources which is not really nice
List<IContainer> containers = null;
Collection<QualifiedName> importedNames = getImportedNames(candidate);
for (IResourceDescription.Delta delta : deltas) {
if (hasChanges(delta, candidate)) {
// not a java resource - delta's resource should be contained in a visible container
// as long as we did not delete the resource
URI uri = delta.getUri();
if ((uri.isPlatform() || uri.isArchive()) && delta.getNew() != null) {
if (containers == null)
containers = containerManager.getVisibleContainers(candidate, context);
boolean descriptionIsContained = false;
for(int i = 0; i < containers.size() && !descriptionIsContained; i++) {
descriptionIsContained = containers.get(i).hasResourceDescription(uri);
}
if (!descriptionIsContained)
return false;
}
if (isAffected(importedNames, delta.getNew()) || isAffected(importedNames, delta.getOld())) {
return true;
}
}
}
return false;
}
What I found is that the two occurrences of the hasChanges(delta, candidate) inside the conditionals of the above codes always return false for our test codes. So, the default
isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions context) always return a false, which prevent the builder to reparse the affected resource. And eventually causes the problem that we have here.
So, for the default hasChanges() method; instead checking whether the file in-edit is changed using hasChanges(delta, candidate), might it be more correct to check whether the file in edit is reparsed and its eResource is recreated? Since hasChanges() eventually calls DefaultResourceDescriptionDelta.haveEObjectDescriptionsChanged(), and it only compares the changes of EObjectDescriptions of the old/new versoin of an eResource, but not the reparse and re-creation of the EObjects for the resource in edit?
Any comments or suggestions?
Yu-Jiu
|
|
|
|
|
Powered by
FUDForum. Page generated in 0.07548 seconds