Home » Modeling » TMF (Xtext) » Class / Method Resolution Issue
Class / Method Resolution Issue [message #1107768] |
Thu, 12 September 2013 22:45 |
David Pace Messages: 19 Registered: March 2013 |
Junior Member |
|
|
Hey all,
I implemented an Xtext-based DSL for an existing object-oriented language. Now I am struggling with the ScopeProvider.
Currently I have lots of classes in my Runtime Eclipse Workspace, which depend on each other (Classes extending other ones, expressions using methods of other classes, etc.)
I injected an instance of IResourceDescriptions to use the Index to resolve classes / methods. (is that the right way?)
The problem:
When I clean the project and it is rebuilt again (lots of resource descriptions are written), all resources have lots of errors, because methods can not be resolved (classes work). However, when I open a resource in the editor, do a small modification (e.g. adding a space or linebreak) the resource is re-validated and all resolution errors disappear. So the ScopeProvider logic seems to be implemented correctly. In "live-editing" mode everything is fine, but not when the workspace is (re)built.
Do you have any clue what could be wrong here?
Maybe it is a problem with the indexing order? When every class inherits from Object, then Object (and the contained methods) must be indexed before the other classes...right? Maybe this reference stays a proxy and that's why it can't be resolved? I had to do some manual resolving in the ScopeProvider, since some instances returned from IResourceDescriptions.getExportedObjects* are proxies.
Additional question regarding performance:
My current implementation is not performance optimized, e.g. for every method call applied to a expression (like someVar.method()) I don't know the type of (it's a dynamically typed language...) I query the IResourceDescriptions for all methods available. Does the IResourceDescriptions implementation use caching/optimization of any kind? Or do I have to care about optimization here?
Thanks for any hints!
Dave
[Updated on: Thu, 12 September 2013 22:54] Report message to a moderator
|
|
| |
Re: Class / Method Resolution Issue [message #1107962 is a reply to message #1107768] |
Fri, 13 September 2013 06:12 |
Ed Merks Messages: 33218 Registered: July 2009 |
Senior Member |
|
|
David,
Just a thought... This sounds like problems I had developing Xcore. In
that case it was very important to respect the second argument of
org.eclipse.xtext.resource.IDerivedStateComputer.installDerivedState(DerivedStateAwareResource,
boolean). I.e., resource descriptions are created without linking so
it's important to be able to load the resource in a way that doesn't
involve resolving proxies during the prelinking phase. It should be
possible to load a resource in order to index it, but that step should
not cause/require any other resources to be loaded (as happens when you
resolve cross document proxies).
On 13/09/2013 12:45 AM, David Hofmann wrote:
> Hey all,
>
> I implemented an Xtext-based DSL for an existing object-oriented
> language. Now I am struggling with the ScopeProvider.
>
> Currently I have lots of classes in my Runtime Eclipse Workspace,
> which depend on each other (Classes extending other ones, expressions
> using methods of other classes, etc.)
>
> I injected an instance of IResourceDescriptions to use the Index to
> resolve classes / methods. (is that the right way?)
>
> The problem:
> When I clean the project and it is rebuilt again (lots of resource
> descriptions are written), all resources have lots of errors, because
> methods can not be resolved (classes work). However, when I open a
> resource in the editor, do a small modification (e.g. adding a space
> or linebreak) the resource is re-validated and all resolution errors
> disappear. So the ScopeProvider logic seems to be implemented
> correctly. In "live-editing" mode everything is fine, but not when the
> workspace is (re)built.
>
> Do you have any clue what could be wrong here?
> Maybe it is a problem with the indexing order? When every class
> inherits from Object, then Object must be indexed before the other
> classes...right? Maybe this reference stays a proxy and that's why it
> can't be resolved?
>
> What made me wonder:
> I had to do some manual resolving in the ScopeProvider, since some
> instances returned from IResourceDescriptions.getExportedObjects* are
> proxies.
>
> Additional question regarding performance:
> My current implementation is not performance optimized, e.g. for every
> method call applied to a expression (like someVar.method()) I don't
> know the type of (it's a dynamically typed language...) I query the
> IResourceDescriptions for all methods available. Does the
> IResourceDescriptions implementation use caching/optimization of any
> kind? Or do I have to care about optimization here?
>
> Thanks for any hints!
> Dave
>
Ed Merks
Professional Support: https://www.macromodeling.com/
|
|
|
Re: Class / Method Resolution Issue [message #1108094 is a reply to message #1107768] |
Fri, 13 September 2013 10:27 |
David Pace Messages: 19 Registered: March 2013 |
Junior Member |
|
|
I'm trying to illustrate this with an example. The goal is to provide a scope for "chained" method calls like this:
identifier.methodA()[.methodB()...]
in some cases I can provide a specific Scope, e.g. for
I only provide a scope for the dynamic methods of the class String. In other cases, like in the following, I don't have any type information (language is dynamically typed) and therefore I have to provide a scope of all methods available in all classes:
printOn { arg stream;
stream.putAll(this.asString);
}
Here we can not know of which type the parameter stream is, but for the instance variable "this" we can provide a more specific scope.
So basically, for all scopes to be provided the algorithm goes like this:
- Look for the element left of the method call
- If the type is derivable, provide a scope of all dynamic methods of that type
- else provide a scope of all methods available in all classes
Here comes the relevant ScopeProvider code:
The declarative entry point:
public IScope scope_ExplicitChainedMethodCall_method(ExplicitChainedMethodCall chainedMethodCall, EReference ref)
{
// get the element left of the chained method call
final EObject contextObject = ScopingUtil.getContextObjectForCallSequence(chainedMethodCall);
// ...and provide a scope for its type (if its derivable)
return getScopeForContextObject(contextObject);
}
The method trying to find out the type. If it cannot derive the type, it returns a scope for all methods (shortened):
public IScope getScopeForContextObject(final EObject contextObject) {
final Resource resource = contextObject.eResource();
if (contextObject instanceof StringLiteral) {
return getDynamicMethodsOfClass("String", resource);
}
[lots of else ifs here ...]
else {
return allMethods();
}
The scope for all methods is retrieved like this:
@Inject
private IResourceDescriptions resourceDescriptions;
private IScope allMethods()
{
final Iterable<IEObjectDescription> methodDeclarations = resourceDescriptions.getExportedObjectsByType(ClassLanguagePackage.Literals.METHOD_DECLARATION);
return new SimpleScope(methodDeclarations);
}
And here are the methods to retrieve dynamic methods for a specific class:
private IScope getDynamicMethodsOfClass(String className, Resource resourceContext) {
final ClassDeclaration classDeclaration = findClassByName(className, resourceContext);
if(classDeclaration != null)
{
return getMethodsOfClass(classDeclaration, false, true);
}
LOGGER.warn("Could not find class by name: " + className);
return IScope.NULLSCOPE;
}
private ClassDeclaration findClassByName(String className, Resource resourceContext) {
final QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(className);
final Iterable<IEObjectDescription> exportedObjects = resourceDescriptions.getExportedObjects(ClassLanguagePackage.Literals.CLASS_DECLARATION, qualifiedName, false);
for (IEObjectDescription ieObjectDescription : exportedObjects) {
final EObject object = ieObjectDescription.getEObjectOrProxy();
if (object instanceof ClassDeclaration) {
ClassDeclaration classDeclaration = (ClassDeclaration) object;
classDeclaration = ScopingUtil.resolveIfNecessary(classDeclaration, resourceContext);
return classDeclaration;
}
}
return null;
}
private IScope getMethodsOfClass(ClassDeclaration classDeclaration, boolean staticMethods, boolean dynamicMethods) {
if(classDeclaration == null || (!staticMethods && !dynamicMethods))
{
return IScope.NULLSCOPE;
}
Collection<MethodDeclaration> methodDeclarations = classDeclaration.getMethods();
if(staticMethods && !dynamicMethods)
{
methodDeclarations = Collections2.filter(methodDeclarations, getStaticMethodDeclarationPredicate());
}
if(!staticMethods && dynamicMethods)
{
methodDeclarations = Collections2.filter(methodDeclarations, getDynamicMethodDeclarationPredicate());
}
ClassDeclaration superClass = getSuperClass(classDeclaration);
IScope scope = Scopes.scopeFor(methodDeclarations, getMethodsOfClass(superClass, staticMethods, dynamicMethods));
return scope;
}
private ClassDeclaration getSuperClass(ClassDeclaration classDeclaration) {
if(classDeclaration == null)
return null;
ClassReference extendedClass = classDeclaration.getExtendedClass();
if(extendedClass == null)
{
final String name = classDeclaration.getName();
if(name == null || name.equals(OBJECT_CLASS_NAME))
{
return null;
}
// the class implicitly extends Object
return findClassByName(OBJECT_CLASS_NAME, classDeclaration.eResource());
}
else
{
ClassDeclaration referencedClass = extendedClass.getReferencedClass();
referencedClass = ScopingUtil.resolveIfNecessary(referencedClass, classDeclaration.eResource());
return referencedClass;
}
}
The manual resolving stuff seems not to be the right way...
However as I said, as soon as I open files in the editor and trigger re-validation, everything is resolved properly (but not when the workspace is built, error markers everywhere).
@Ed: Thanks for your hint! I never worked with that interface, in which context do you use / call it?
|
|
| |
Re: Class / Method Resolution Issue [message #1108116 is a reply to message #1108094] |
Fri, 13 September 2013 11:12 |
Ed Merks Messages: 33218 Registered: July 2009 |
Senior Member |
|
|
David,
If you're extending Xbase (I assumed you were but maybe not) then you'd
have
public Class<? extends org.eclipse.xtext.resource.IDerivedStateComputer>
bindIDerivedStateComputer() {
return org.eclipse.xtext.xbase.jvmmodel.JvmModelAssociator.class;
}
in your runtime module. That's something I needed to specialize for
Xcore, and something I needed to do carefully in a way that avoids
needing to do scoped lookups to resolve type names during the preLinking
phase...
Sorry if this isn't relevant for your scenario. It's just that your
symptoms sound exactly like the ones I saw, i.e., errors from the
validator run by the builder that just go away when you open the thing
in the editor....
On 13/09/2013 12:28 PM, David Hofmann wrote:
> I'm trying to illustrate this with an example. The goal is to provide
> a scope for "chained" method calls like this:
>
> identifier.methodA()[.methodB()...]
>
> in some cases I can provide a specific Scope, e.g. for
>
> "myString".asSymbol
>
> I only provide a scope for the dynamic methods of the class String. In
> other cases, like in the following, I don't have any type information
> (language is dynamically typed) and therefore I have to provide a
> scope of all methods available in all classes:
>
> printOn { arg stream;
> stream.putAll(this.asString);
> }
>
> Here we can not know of which type the parameter stream is, but for
> the instance variable "this" we can provide a more specific scope.
>
> So basically, for all scopes to be provided the algorithm goes like this:
> - Look for the element left of the method call
> - If the type is derivable, provide a scope of all dynamic methods of
> that type
> - else provide a scope of all methods available in all classes
>
> Here comes the relevant ScopeProvider code:
>
> The declarative entry point:
>
> public IScope
> scope_ExplicitChainedMethodCall_method(ExplicitChainedMethodCall
> chainedMethodCall, EReference ref)
> {
> // get the element left of the chained method call
> final EObject contextObject =
> ScopingUtil.getContextObjectForCallSequence(chainedMethodCall);
> // ...and provide a scope for its type (if its derivable)
> return getScopeForContextObject(contextObject);
> }
>
> The method trying to find out the type. If it cannot derive the type,
> it returns a scope for all methods (shortened):
>
> public IScope getScopeForContextObject(final EObject contextObject) {
> final Resource resource = contextObject.eResource();
> if (contextObject instanceof StringLiteral) {
> return getDynamicMethodsOfClass("String", resource);
> }
> [lots of else ifs here ...]
> else {
> return allMethods();
> }
>
>
> The scope for all methods is retrieved like this:
>
>
> @Inject
> private IResourceDescriptions resourceDescriptions;
>
> private IScope allMethods()
> {
> final Iterable<IEObjectDescription> methodDeclarations =
> resourceDescriptions.getExportedObjectsByType(ClassLanguagePackage.Literals.METHOD_DECLARATION);
>
> return new SimpleScope(methodDeclarations);
> }
>
>
> And here are the methods to retrieve dynamic methods for a specific
> class:
>
>
> private IScope getDynamicMethodsOfClass(String className, Resource
> resourceContext) {
> final ClassDeclaration classDeclaration =
> findClassByName(className, resourceContext);
> if(classDeclaration != null)
> {
> return getMethodsOfClass(classDeclaration, false, true);
> }
> LOGGER.warn("Could not find class by name: " + className);
> return IScope.NULLSCOPE;
> }
>
> private ClassDeclaration findClassByName(String className, Resource
> resourceContext) {
> final QualifiedName qualifiedName =
> qualifiedNameConverter.toQualifiedName(className);
> final Iterable<IEObjectDescription> exportedObjects =
> resourceDescriptions.getExportedObjects(ClassLanguagePackage.Literals.CLASS_DECLARATION,
> qualifiedName, false);
> for (IEObjectDescription ieObjectDescription : exportedObjects) {
> final EObject object =
> ieObjectDescription.getEObjectOrProxy();
> if (object instanceof ClassDeclaration) {
> ClassDeclaration classDeclaration = (ClassDeclaration)
> object;
> classDeclaration =
> ScopingUtil.resolveIfNecessary(classDeclaration, resourceContext);
> return classDeclaration;
> }
> }
>
> return null;
> }
>
> private IScope getMethodsOfClass(ClassDeclaration classDeclaration,
> boolean staticMethods, boolean dynamicMethods) {
> if(classDeclaration == null || (!staticMethods &&
> !dynamicMethods))
> {
> return IScope.NULLSCOPE;
> }
>
> Collection<MethodDeclaration> methodDeclarations =
> classDeclaration.getMethods();
>
> if(staticMethods && !dynamicMethods)
> {
> methodDeclarations =
> Collections2.filter(methodDeclarations,
> getStaticMethodDeclarationPredicate());
> }
>
> if(!staticMethods && dynamicMethods)
> {
> methodDeclarations =
> Collections2.filter(methodDeclarations,
> getDynamicMethodDeclarationPredicate());
> }
>
> ClassDeclaration superClass = getSuperClass(classDeclaration);
> IScope scope = Scopes.scopeFor(methodDeclarations,
> getMethodsOfClass(superClass, staticMethods, dynamicMethods));
> return scope;
>
> }
>
> private ClassDeclaration getSuperClass(ClassDeclaration
> classDeclaration) {
> if(classDeclaration == null)
> return null;
>
> ClassReference extendedClass =
> classDeclaration.getExtendedClass();
> if(extendedClass == null)
> {
> final String name = classDeclaration.getName();
> if(name == null || name.equals(OBJECT_CLASS_NAME))
> {
> return null;
> }
>
> // the class implicitly extends Object
> return findClassByName(OBJECT_CLASS_NAME,
> classDeclaration.eResource());
> }
> else
> {
> ClassDeclaration referencedClass =
> extendedClass.getReferencedClass();
> referencedClass =
> ScopingUtil.resolveIfNecessary(referencedClass,
> classDeclaration.eResource());
> return referencedClass;
> }
> }
>
>
> The manual resolving stuff seems not to be the right way...
> However as I said, as soon as I open files in the editor and trigger
> re-validation, everything is resolved properly (but not when the
> workspace is built, error markers everywhere).
>
> @Ed: Thanks for your hint! I never worked with that interface, in
> which context do you use / call it?
Ed Merks
Professional Support: https://www.macromodeling.com/
|
|
| | | |
Re: Class / Method Resolution Issue [message #1108953 is a reply to message #1108946] |
Sat, 14 September 2013 17:21 |
|
Hi,
simplenamesfragment looks perfect.
i have no idea what happens.
there must be something wrong in your code.
what happens if you do not implement scoping at all?
can you share complete copy & pasteable code?
and regarding the delegate get scope
simpy use object and erefernce from your scoping method.
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
Re: Class / Method Resolution Issue [message #1110459 is a reply to message #1107768] |
Mon, 16 September 2013 23:12 |
David Pace Messages: 19 Registered: March 2013 |
Junior Member |
|
|
Wow cool, if I uncomment my declarative scoping methods the method resolution works both in the editor and in the workspace build (seems like my workflow fragments were not configured properly when I started to mess around with the ScopeProvider...)
However, now the resolution is only based on the name (e.g. does not respect static/dynamic context). I'm afraid the problem with multiple methods with the same name will appear frequently in a dynamically typed language anyway...Should I narrow the scopes based on the left element or simply leave it this way? What about Auto-Completion? I could restrict the proposals there but I thought it is better to restrict the scope (the "root" problem, so to say).
In the original "IDE" for the language, when trying to "jump" (CTRL+Click in Eclipse) to a method which is not unambigously resolvable, there is a dialog in which the classes having methods with that name are selectable. Is there a place where I can implement this custom behaviour with Xtext?
And there is another thing. Consider this class with some members:
MyClass
{
var <a;
var >b;
classvar <>c;
}
The < sign next to the member a implies that there is a virtual getter method for that field, the > sign implies a setter method, named like the member followed by an underscore. The member c is both getable and setable in the static context. That means, the following expressions are valid:
instance.a();
instance.b_(value)
MyClass.c();
MyClass.c_(value)
My question is: Where is the best place where I can create those "virtual" derived methods (a, b_, c and c_) so that they can be resolved?
Thanks for your hints!
|
|
| | | | | | |
Re: Class / Method Resolution Issue [message #1219799 is a reply to message #1219798] |
Thu, 05 December 2013 20:14 |
David Pace Messages: 19 Registered: March 2013 |
Junior Member |
|
|
it basically goes like this:
member declaration <a -> virtual getter method a()
member declaration >a -> virtual setter method a_(value)
member declaration <>a -> virtual getter a() and setter a_(value)
The same is possible in the static context, but I'm gonna solve that afterwards, provided that we can find a solution here ^^
Thanks for trying to help!
[Updated on: Thu, 05 December 2013 20:15] Report message to a moderator
|
|
| | | | | | |
Goto Forum:
Current Time: Thu Sep 26 18:45:25 GMT 2024
Powered by FUDForum. Page generated in 0.07249 seconds
|