Eclipse Xtext - Scoping

On the last page we looked at calling a Inheriting from a DSL. On this page we look at scoping in DSLs:

Scoping allows the language designer to decide how non-containment references are handled.

Non-containment references are represented in the Ecore model by 'EReference'.

'getScope' is a method injected from IScopeProvider. Given an EObject, and an EReference from it, then getScope will return an IScope containing all the objects that can be reached from it. They may not all be semantically valid.

scoping

Example 1 - non-Xbase

When we were looking at how to generate non-containment links (on this page) we used the example from the xtext documentation.

On the right I have written some code using this DSL. I have deliberately used the same IDs multiple times so that we can see how these references are resolved using scopes.
datatype mydata
datatype mydata2
entity mydata {
	Name:mydata
	many mydata:mydata
}
entity mydata2 {
	Name:mydata
	many mydata:mydata
}

Here is the scope hierarchy for that DSM (see that page for the grammar and diagram of the ecore model):

On this diagram:

  • The green boxes represent an instance of IScope.
  • In this example the EReference is looking for the ID='mydata'. In other examples there may be multiple scope trees looking for different IDs.

The text in the green boxes show:

  • The type of IScope instance.
  • import normalisers (in first square brackets).
  • all local elements (in second square brackets).

The black arrows point to the parent scope.

scoping non xbase example

As you can see on the ecore model (here) there is a non-containment link from 'Feature' to 'Type'. That is from lines like 'Name:mydata' and 'many mydata:mydata' to lines like 'datatype mydata'. These links are formed by setting the ID so that it corresponds to the ID we want to link to.

Scopes are defined for the start of these links, in this case 'Feature' nodes (like 'Name:mydata' and 'many mydata:mydata'). So when we are resolving these links we use the node we are calling from (context) and the reference and we call:

getScope(context,reference)

This returns an instance of IScope (as in the green boxes above).

As we can see in the diagram above, the 'Feature' nodes do not have any matches (second square bracket is empty) so we then go to the parent scope (follow the black arrow) until we find a match. In this case we have to go up to the 'SelectableBasedScope' until we find a match, that is the top level where we have the 'Type' entries as required.

Scoping in XBase Projects

Scoping in Xbase applications is more complicated due to the extra indirection involved with Xbase (as discussed here).

To understand this we need to know the XBase and Types models we need to know where the non-containment links are, that we want to customise the scoping of.

There are lots of EReferences in the model such as the caller of a method references its definition and the user of a class references its definition.

The non-containment EReferences in the JVM Type model are:

Source Reference Destination Notes
JvmArrayType componentType JvmComponentType  
JvmTypeParameter declarator JvmTypeParameterDeclarator  
JvmTypeConstraint owner JvmConstraintOwner  
JvmEnumerationType [literals] JvmEnumerationLiterals  
JvmParameterizedTypeReference type JvmType

Reference to variable in expression.

May be contained in X

JvmAnyTypeReference type JvmType  
JvmMember declaringType JvmDeclaredType  
JvmAnnotationReference annotation JvmAnnotationType  
JvmAnnotationReference target JvmAnnotationTarget  
JvmAnnotationValue operation JvmOperation  
JvmAnnotationAnnotationValue [values] JvmAnnotationReference  
JvmEnumAnnotationValue values JvmEnumerationLiteral  
JvmDelegateTypeReference delegate JvmTypeReference  
JvmCompoundTypeReference type JvmType  

notes: references in brackets are arrays.

The non-containment EReferences in the XBase model are:

Source Reference Destination Notes
XAbstractFeatureCall feature JvmIdentifiableElement  
XFeatureCall declaringType JvmDeclaredType  
XConstructorCall constructor JvmConstructor  
XTypeLiteral type JvmType  

Example 2 - Xbase

In the example here I have deliberately used the same names (myName1 and myName 2) multiple times in order to investigate how scoping works in this example.

These names are used on the right hand side of the equation in 4 places, like this,

val int myName1 = myName1

These references need to be matched to appropriate definitions and this is where the scoping infrastructure is used.

package example6
import example6

class example61 {
  val int myName1 = 1
  val int myName2 = 2

  def int myName1() {
    val int myName1 = myName1
    val int myName2 = myName2
    return myName1
  }

  def int myName2() {
    val int myName1 = myName1
    val int myName2 = myName2
    return myName2
  }
}

So lets look at the first case: 'val int myName1 = myName1' in the class example 61. The 'myName1' needs to be mapped to a value definition somewhere so this must involve an 'EReference' somewhere.

However there are no EReferences in our user(language designer) code so the EReferences must be in the xbase model.

In face this is in a 'JvmParameterizedTypeReference' my code has a 'MyTestFieldImpl' container which holds this.

platform:/resource/mytest/src/example6/example61.dsl#xtextLink_::0.2.0.6.0.3::1::/0) initial=org.eclipse.xtext.xbase.impl.XNumberLiteralImpl@35298697 (value: 1)
reference name=type type=JvmType keys=[] containment=false

The scope hierarchy at this point is:

This is the scope hierarchy only from the line
'val int myName1 = myName1'

xbase scope

Something not right here as none of the possible matches seem to be found in the scope?

Example 3 - Inner Class Scoping.

I would like to define the visibility when:

The following xbase example uses our usual grammar and jvmModelInferrer files. It uses default validation and scoping handling (no files other than those generated automatically).

The contents of methods are an XBlockExpression and so are handled automatically by xbase. As we can see from the screenshot, there are problems with referring to outer function from inner function.

scoping

More about inner classes, using xbase, on this page.

Code

  Link to code
A scope defines which elements IEObjectDescription can be seen in a certain area within a model/program. In other words: A scope is a kind of container structure that provides access to all objects that can be reached from a given. IScope
An IScopeProvider can be used, to get access to a set of visible elements interface IScopeProvider IScopeProvider
XbaseImportedNamespaceScopeProvider XbaseImportedNamespaceScopeProvider
This class contains static utility functions to create and work on IScope and IEObjectDescription Scopes
  ImportScope
An ISelectable is something that can be queried for exported object. Thereby it serves as a common abstract super concept for IContainer and IResourceDescription. Furthermore there is an implementation of the org.eclipse.xtext.scoping.IScope IScope interface that is based on link ISelectable and vice versa. All implementations of ISelectable can deal with ignore case and case sensitive queries. ISelectable
  ImportNormalizer
An abstract description of an EObject. IEObjectDescription

Cant find this? Is it an old version of IEObjectDescription?

org.eclipse.xtext.scoping.IScopedElement

IScopedElement

Further Reading

This site:
There is more about cross-referencing, on this page.
For more information about naming go on to this page.

Other sites:
This itemis blog has more information about this topic.
Also this blog by Sebastian Zarnekow.
and this blog by Sven Efftinge
(The last two links are very old and so refer to URL names as opposed to namespaces)
Andreas Graf blog here

public interface IScopeProvider extends ILanguageService {  
  IScope getScope(EObject context, EReference reference);  
}  
  
public interface IScope { 
  IScope getOuterScope();  
  Iterable getContents();  
  /* .. */  
}      
  
public interface IScopedElement { 
  String name();  
  EObject element();  
  /* .. */  
} 
class XbaseImportedNamespaceScopeProvider
	---------------------------------------------------------------
IQualifiedNameProvider getQualifiedNameProvider()
IScope getScope(EObject context, EReference reference)
IScope getGlobalScope(final Resource context, final EReference reference)
IScope internalGetScope(IScope parent, IScope globalScope, EObject context, EReference reference)
IScope getLocalElementsScope(IScope parent, IScope globalScope, EObject context, EReference reference)
IScope getResourceScope(IScope globalScope,Resource res, EReference reference)
List getImplicitImports(boolean ignoreCase)
ImportScope createImportScope(IScope parent, List namespaceResolvers, ISelectable importFrom, EClass type, boolean ignoreCase)
Object getKey(Notifier context, EReference reference)
List internalGetImportedNamespaceResolvers(final EObject context, boolean ignoreCase)
ImportNormalizer createImportedNamespaceResolver(final String namespace, boolean ignoreCase)
ImportNormalizer doCreateImportNormalizer(QualifiedName importedNamespace, boolean wildcard,boolean ignoreCase)
QualifiedName getQualifiedNameOfLocalElement(final EObject context)
ISelectable getAllDescriptions(final Resource resource)
ISelectable internalGetAllDescriptions(final Resource resource)

metadata block
see also: itemis blog
Correspondence about this page

This site may have errors. Don't use for critical systems.

Copyright (c) 1998-2022 Martin John Baker - All rights reserved - privacy policy.