Scoping, Linking and Indexing
Jan Köhnlein / Moritz Eysholdt – itemis AG
Agenda
I. Overview
II. Scope Usage
III. Scope Implementation
IV. The Index
V. Best Practices
I. Overview
EMF: Containment vs Cross References
Containment references form trees
Cross references extend trees to graphs
Cross References in DSLs
:PlacesModel
Hamburg:Place Berlin:Place Sven:Person
place Hamburg	
place Berlin	
!
person Sven visited Hamburg
PlacesModel:	
	 (persons+=Person | places+=Place)*;	
!
Place:	
	 "place" name=ID ("{" 	
	 	 (persons+=Person | places+=Place)*	
	 "}")?;	
!
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
References in Xtext Grammars
PlacesModel:	
	 (persons+=Person | places+=Place)*;	
!
Place:	
	 "place" name=ID ("{" 	
	 	 (persons+=Person | places+=Place)*	
	 "}")?;	
!
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
Containment References in Xtext Grammars
containment reference
rule
PlacesModel:	
	 (persons+=Person | places+=Place)*;	
!
Place:	
	 "place" name=ID ("{" 	
	 	 (persons+=Person | places+=Place)*	
	 "}")?;	
!
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
Cross References in Xtext Grammars
cross reference
target EClass identifier syntax
Cross References in Xtext Editors
Hyperlink
Validation
ContentAssist
Hover
FindReferences
Cross References in Xtext Editors
Hyperlink
Validation
ContentAssist
Hover
FindReferences
Cross References in Xtext Editors
Hyperlink
Validation
ContentAssist
Hover
FindReferences
Cross References in Xtext Editors
Hyperlink
Validation
ContentAssist
Hover
FindReferences
Cross References in Xtext Editors
Hyperlink
Validation
ContentAssist
Hover
FindReferences
II. Scope Usage
Scopes: Xtext’s abstraction to relate names and model elements
Linking
ContentAssist
Serializing
If you understand how Xtext uses scopes,
you will know how to implement your scopes.
The Three Use Cases:
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
Linking
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = EcoreUtil.resolve(element.getEObjectOrProxy(), context);	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Parsed Document
Grammar
Xtext’s linking implementation
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) find target EObject of cross reference “visited”.
Name must be “Hamburg”.
Type must be “Place”
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(name);	
EObject target = element.getEObjectOrProxy();	
if (type.isInstance(target)) {	
context.eSet(reference, target);	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
ContentAssist
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) and cross reference “visited”,
find the names of all valid target elements.
IScope scope = scopeProvider.getScope(context, reference);	
Iterable<IEObjectDescription> elements = scope.getAllElements();	
	 	 	 	
List<QualifiedName> names = new ArrayList<>();	
for(IEObjectDescription desc : elements) {	
names.add(desc.getName());	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
place Hamburg	
!
person Sven visited Hamburg
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
For context (Person “Sven”) and cross reference “visited”,
find the names of all valid target elements.
IScope scope = scopeProvider.getScope(context, reference);	
Iterable<IEObjectDescription> elements = scope.getAllElements();	
	 	 	 	
List<QualifiedName> names = new ArrayList<>();	
for(IEObjectDescription desc : elements) {	
names.add(desc.getName());	
}
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
Serializing
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
Serializing
XtextResource
ModelModelTextual!
Model
AST
Parser
Serializer
load()
save()
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(target);	
QualifiedName name = element.getName();
For context (Person “Sven”), cross reference “visited” and target “Hamburg”
find the target’s name.
model:EObject
name=Sven
Sven:EObject
name=Hamburg
Hamburg:EObject
Scopes: The Three Use Cases:
Linking
ContentAssist
Serializing
Person:	
	 "person" name=ID "visited" visited=[Place|ID];
IScope scope = scopeProvider.getScope(context, reference);	
IEObjectDescription element = scope.getSingleElement(target);	
QualifiedName name = element.getName();
For context (Person “Sven”), cross reference “visited” and target “Hamburg”
find the target’s name.
model:EObject
name=Sven
Sven:EObject
name=Hamburg
Hamburg:EObject
IScope scope = scopeProvider.getScope(context, reference);	
!
!
!
scope.getSingleElement(name);	
!
!
scope.getAllElements();	
!
!
scope.getSingleElement(target);
Scopes: The Three Use Cases
Linking
ContentAssist
Serializing
IEObjectDescription
public interface IEObjectDescription {	
	 	
	 QualifiedName getName();	
!
	 EObject getEObjectOrProxy();	
	 	
	 EClass getEClass();	
	 	
	 String getUserData(String key);	
	 	
	 String[] getUserDataKeys();	 	
!
	 (…)	
	 	
}
• Handle for model element to avoid loading
• Name and type for model element
• User data
• Can be persisted in index
• Multiple descriptions for same EObject
Utils
QualifiedName name = QualifiedName.create("org", "eclipse", "xtext");	
	 	 	 	
IEObjectDescription description = EObjectDescription.create(name, element);	
	 	 	 	
IScope scope1 = Scopes.scopeFor(eObjectList);	
IScope scope2 = new SimpleScope(eObjectDescriptionList);
III. Scope Implementation
Default Scoping Configuration
fragment = scoping.ImportNamespacesScopingFragment {}	
fragment = exporting.QualifiedNamesFragment {}
• Uses value of attribute “name“
• References use qualified names
• Import statements for qualified names
• Name shadowing
• Cross-file references using index
• Honors project dependencies
Inside a Scope
place Hamburg	
place Berlin	
place Kiel	
!
person Sven visited Hamburg
Hamburg
Berlin
Kiel
getScope(personSven, referenceVisited)
<returns>
Inside a Scope
place Hamburg	
place Berlin	
place Kiel	
!
person Sven visited Hamburg
Hamburg
Berlin
Kiel
getScope(personSven, referenceVisited)
<returns>
Inside a Scope: Nesting
place Germany {	
	 person Sven visited Brazil	
	 place Kiel	
}	
!
place Brazil
Kiel
getScope(personSven, referenceVisited)
<returns>
Germany
Germany.Kiel
Brazil
<parent>
Delegate to parent scope if name not found.
Inside a Scope: Nesting
place Germany {	
	 person Sven visited Brazil	
	 place Kiel	
}	
!
place Brazil
Kiel
getScope(personSven, referenceVisited)
<returns>
Germany
Germany.Kiel
Brazil
<parent>
Inside a Scope: Nesting
place Germany {	
	 person Sven visited Brazil	
	 place Kiel	
}	
!
place Brazil
Kiel
getScope(personSven, referenceVisited)
<returns>
Germany
Germany.Kiel
Brazil
<parent>
Delegate to parent scope if name not found.
Inside a Scope: Name Shadowing
place Kiel {	
	 person Sven visited Brazil	
	 place Brazil	
}	
!
place Brazil
Brazil
getScope(personSven, referenceVisited)
<returns>
Brazil
Kiel
Kiel.Brazil
<parent>
Inside a Scope: Name Shadowing
place Kiel {	
	 person Sven visited Brazil	
	 place Brazil	
}	
!
place Brazil
Brazil
getScope(personSven, referenceVisited)
<returns>
Brazil
Kiel
Kiel.Brazil
<parent>
Qualified Names
• Qualified Names consist of one or more segments
• Qualified Names are expected to be unique in a specific context
• Bind IQualifiedNameProvider in your RuntimeModule
• Default: Collect values of name attributes of all containers
fragment = exporting.QualifiedNamesFragment {}
Qualified Names in the Grammar
Person:	
	 "person" name=ID "visited" 	
	 	 // visited=[Place] // bad, defaults to ID	
	 	 // visited=[Place|ID] // bad, ID does not allow “.”	
	 	 visited=[Place|FQN]; // good	
!
FQN:	
	 ID ("." ID)*;
• If you extend Xbase.xtext or Xtype.xtext, they have a rule for QualifiedNames
• If you defined your own, you may use
org.eclipse.xtext.conversion.impl.QualifiedNameValueConverter
Inside a Scope: Name Qualification
place Germany {	
	 place Kiel {	
	 	 person Sven visited France.Paris	
	 	 place Brazil	
	 }	
	 place Hamburg	
}	
place France {	
	 place Paris	
}
BrazilgetScope(personSven, referenceVisited)
<returns>
Kiel
Kiel.Brazil
Hamburg
<parent>
Germany
Germany.Kiel
Germany.Kiel.Brazil
Germany.Hamburg
France
France.Paris
<parent>
place Germany {	
	 place Kiel {	
	 	 person Sven visited France.Paris	
	 	 place Brazil	
	 }	
	 place Hamburg	
}	
place France {	
	 place Paris	
}
Inside a Scope: Name Qualification
BrazilgetScope(personSven, referenceVisited)
<returns>
Kiel
Kiel.Brazil
Hamburg
<parent>
Germany
Germany.Kiel
Germany.Kiel.Brazil
Germany.Hamburg
France
France.Paris
<parent>
Default Implementation for Imports
fragment = scoping.ImportNamespacesScopingFragment {}
Import:	
	 "import" importedNamespace=FQNWithWildcard;	
!
FQNWithWildcard:	
	 FQN ("." "*")?;
MyLanguageScopeProvider
ImportedNamespaceAwareLocalScopeProvider
Default Implementation for Cross-File References
fragment = scoping.ImportNamespacesScopingFragment {}
MyLanguageScopeProvider
ImportedNamespaceAwareLocalScopeProvider
DefaultGlobalScopeProvider
Index
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places
• Import via FQN
• Import via wildcard
• Cross-file without import
• Imports are filename agnostic
• Local names have precedence
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places Q: Why did Alex not
visit Brazil close to Kiel?
Cross-File References
place SouthAmerica { place Brazil }	
place USA { place California }
west.places
import USA.*	
import SouthAmerica.Brazil	
!
place Kiel {	
	 person Jan visited California	
	 person Sven visited Brazil	
	 person Anton visited SouthAmerica.Brazil	
	 place Brazil	
}	
place Luenen {	
	 person Alex visited Brazil 	
}
home.places Q: Why did Alex not
visit Brazil close to Kiel?
A: For context “Lünen”
it has the name
“Kiel.Brazil”
Implementing own Scopes (extends AbstractDeclarativeScopeProvider)
public class PlacesScopeProvider extends AbstractDeclarativeScopeProvider {	
!
	 // method is called reflectively via name convention	
	 public IScope scope_Person_visited(Person person, EReference reference) {	
	 	 PlacesModel model = EcoreUtil2.getContainerOfType(person, PlacesModel.class);	
	 	 List<Place> allPlaces = EcoreUtil2.getAllContentsOfType(model, Place.class);	
	 	 return Scopes.scopeFor(allPlaces);	
	 }	
}
• need unit tests to guarantee method is called
• java.reflect cumbersome during debugging
• first parameter (context) may be less specific if parser is in lookahead
• don’t forget to register an exception handler, otherwise exceptions are ignored
Implementing own Scopes (implements IScopeProvider)
public class PlacesScopeProvider implements IScopeProvider {	
!
	 @Inject // obtain the next ScopeProvider in the hierarchy	
	 @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE)	
	 private IScopeProvider delegate;	
!
	 // match references and dispatch to specific implementation	
	 public IScope getScope(EObject context, EReference reference) {	
	 	 if (reference == PlacesPackage.Literals.PERSON__VISITED) {	
	 	 	 PlacesModel model = EcoreUtil2.getContainerOfType(context, PlacesModel.class);	
	 	 	 return getPersonVisitedScope(model);	
	 	 }	
	 	 return delegate.getScope(context, reference);	
	 }	
!
	 // create scope for PERSON__VISITED	
	 private IScope getPersonVisitedScope(PlacesModel model) {	
	 	 	
	 	 // if we can't create a scope, NEVER return null but return IScope.NULLSCOPE instead 	
	 	 if (model == null)	
	 	 	 return IScope.NULLSCOPE;	
	 	 	
	 	 // build some scope. 	
	 	 // This one includes all elements from the same file by their simple names	
	 	 List<Place> allPlaces = EcoreUtil2.getAllContentsOfType(model, Place.class);	
	 	 return Scopes.scopeFor(allPlaces);	
	 }	
}
IV. The Index
Use cases for the Index
• Filename-agnostic resolution of names	

• Validation of cross-references without loading targets	

• Computation of affected files during incremental build	

• Reverse-reference lookup
Interactions with the Index
Index
Builder
Scoping
<updates, reads>
<reads>
<reads>
<reads>
Data Structure of the Index (simplified)
ResourceDescriptions
uri : URI
importedNames : List<String>
ResourceDescription
sourceEObjectURI : URI
targetEObjectURI : URI
ReferenceDescription
name : String
eObjectFragment: String
userData : Map<String, String>
EObjectDescription
[0..*][0..*]
[0..*]
Index Population
incremental Build
resource change <triggers>
1. Index Exported Names
2. Resolve References
and Validate
Index Population
incremental Build
resource change <triggers>
During computation of exported names,
you CANNOT resolve references.
1. Index Exported Names
2. Resolve References
and Validate
Customize Indexing of an Xtext-Language #1
public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() {	
return MyQualifiedNameProvider.class;	
}
MyLanguageRuntimeModule
public interface IQualifiedNameProvider extends Function<EObject, QualifiedName> {	
!
	 /**	
	 * @return the qualified name for the given object, 	
	 * <code>null</code> if this {@link IQualifiedNameProvider} is not	
	 * responsible or if the given object doesn't have qualified name.	
	 */	
	 QualifiedName getFullyQualifiedName(EObject obj);	
}
Customize Indexing of an Xtext-Language #2
@ImplementedBy(DefaultResourceDescriptionStrategy.class)	
public interface IDefaultResourceDescriptionStrategy {	
!
	 /**	
	 * Calculates the {@link IEObjectDescription}s for 	
	 * <code>eObject</code> and passes them to the acceptor.	
	 * 	
	 * @return true if the children of <code>eObject</code> should be traversed.	
	 */	
	 boolean createEObjectDescriptions(	
EObject eObject, 	
IAcceptor<IEObjectDescription> acceptor	
);	
!
	 /** …	 */	
	 boolean createReferenceDescriptions(	
EObject eObject, 	
URI exportedContainerURI, 	
IAcceptor<IReferenceDescription> acceptor	
);	
}
V. Best Practices
Best Practices
• Do not scope to restrictively…
• …rather adapt validation and content assist.
• Avoid resolving elements when filtering scopes.
• Names should not depend on cross-references.
That’s all, folks!

Scoping

  • 1.
    Scoping, Linking andIndexing Jan Köhnlein / Moritz Eysholdt – itemis AG
  • 2.
    Agenda I. Overview II. ScopeUsage III. Scope Implementation IV. The Index V. Best Practices
  • 3.
  • 4.
    EMF: Containment vsCross References Containment references form trees Cross references extend trees to graphs
  • 5.
    Cross References inDSLs :PlacesModel Hamburg:Place Berlin:Place Sven:Person place Hamburg place Berlin ! person Sven visited Hamburg
  • 6.
    PlacesModel: (persons+=Person |places+=Place)*; ! Place: "place" name=ID ("{" (persons+=Person | places+=Place)* "}")?; ! Person: "person" name=ID "visited" visited=[Place|ID]; References in Xtext Grammars
  • 7.
    PlacesModel: (persons+=Person |places+=Place)*; ! Place: "place" name=ID ("{" (persons+=Person | places+=Place)* "}")?; ! Person: "person" name=ID "visited" visited=[Place|ID]; Containment References in Xtext Grammars containment reference rule
  • 8.
    PlacesModel: (persons+=Person |places+=Place)*; ! Place: "place" name=ID ("{" (persons+=Person | places+=Place)* "}")?; ! Person: "person" name=ID "visited" visited=[Place|ID]; Cross References in Xtext Grammars cross reference target EClass identifier syntax
  • 9.
    Cross References inXtext Editors Hyperlink Validation ContentAssist Hover FindReferences
  • 10.
    Cross References inXtext Editors Hyperlink Validation ContentAssist Hover FindReferences
  • 11.
    Cross References inXtext Editors Hyperlink Validation ContentAssist Hover FindReferences
  • 12.
    Cross References inXtext Editors Hyperlink Validation ContentAssist Hover FindReferences
  • 13.
    Cross References inXtext Editors Hyperlink Validation ContentAssist Hover FindReferences
  • 14.
  • 15.
    Scopes: Xtext’s abstractionto relate names and model elements Linking ContentAssist Serializing If you understand how Xtext uses scopes, you will know how to implement your scopes. The Three Use Cases:
  • 16.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing Linking
  • 17.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = EcoreUtil.resolve(element.getEObjectOrProxy(), context); if (type.isInstance(target)) { context.eSet(reference, target); } Parsed Document Grammar Xtext’s linking implementation
  • 18.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 19.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 20.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 21.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 22.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 23.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) find target EObject of cross reference “visited”. Name must be “Hamburg”. Type must be “Place” IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(name); EObject target = element.getEObjectOrProxy(); if (type.isInstance(target)) { context.eSet(reference, target); }
  • 24.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing ContentAssist
  • 25.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) and cross reference “visited”, find the names of all valid target elements. IScope scope = scopeProvider.getScope(context, reference); Iterable<IEObjectDescription> elements = scope.getAllElements(); List<QualifiedName> names = new ArrayList<>(); for(IEObjectDescription desc : elements) { names.add(desc.getName()); }
  • 26.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing place Hamburg ! person Sven visited Hamburg Person: "person" name=ID "visited" visited=[Place|ID]; For context (Person “Sven”) and cross reference “visited”, find the names of all valid target elements. IScope scope = scopeProvider.getScope(context, reference); Iterable<IEObjectDescription> elements = scope.getAllElements(); List<QualifiedName> names = new ArrayList<>(); for(IEObjectDescription desc : elements) { names.add(desc.getName()); }
  • 27.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing Serializing
  • 28.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing Serializing XtextResource ModelModelTextual! Model AST Parser Serializer load() save()
  • 29.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing Person: "person" name=ID "visited" visited=[Place|ID]; IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(target); QualifiedName name = element.getName(); For context (Person “Sven”), cross reference “visited” and target “Hamburg” find the target’s name. model:EObject name=Sven Sven:EObject name=Hamburg Hamburg:EObject
  • 30.
    Scopes: The ThreeUse Cases: Linking ContentAssist Serializing Person: "person" name=ID "visited" visited=[Place|ID]; IScope scope = scopeProvider.getScope(context, reference); IEObjectDescription element = scope.getSingleElement(target); QualifiedName name = element.getName(); For context (Person “Sven”), cross reference “visited” and target “Hamburg” find the target’s name. model:EObject name=Sven Sven:EObject name=Hamburg Hamburg:EObject
  • 31.
    IScope scope =scopeProvider.getScope(context, reference); ! ! ! scope.getSingleElement(name); ! ! scope.getAllElements(); ! ! scope.getSingleElement(target); Scopes: The Three Use Cases Linking ContentAssist Serializing
  • 32.
    IEObjectDescription public interface IEObjectDescription{ QualifiedName getName(); ! EObject getEObjectOrProxy(); EClass getEClass(); String getUserData(String key); String[] getUserDataKeys(); ! (…) } • Handle for model element to avoid loading • Name and type for model element • User data • Can be persisted in index • Multiple descriptions for same EObject
  • 33.
    Utils QualifiedName name =QualifiedName.create("org", "eclipse", "xtext"); IEObjectDescription description = EObjectDescription.create(name, element); IScope scope1 = Scopes.scopeFor(eObjectList); IScope scope2 = new SimpleScope(eObjectDescriptionList);
  • 34.
  • 35.
    Default Scoping Configuration fragment= scoping.ImportNamespacesScopingFragment {} fragment = exporting.QualifiedNamesFragment {} • Uses value of attribute “name“ • References use qualified names • Import statements for qualified names • Name shadowing • Cross-file references using index • Honors project dependencies
  • 36.
    Inside a Scope placeHamburg place Berlin place Kiel ! person Sven visited Hamburg Hamburg Berlin Kiel getScope(personSven, referenceVisited) <returns>
  • 37.
    Inside a Scope placeHamburg place Berlin place Kiel ! person Sven visited Hamburg Hamburg Berlin Kiel getScope(personSven, referenceVisited) <returns>
  • 38.
    Inside a Scope:Nesting place Germany { person Sven visited Brazil place Kiel } ! place Brazil Kiel getScope(personSven, referenceVisited) <returns> Germany Germany.Kiel Brazil <parent> Delegate to parent scope if name not found.
  • 39.
    Inside a Scope:Nesting place Germany { person Sven visited Brazil place Kiel } ! place Brazil Kiel getScope(personSven, referenceVisited) <returns> Germany Germany.Kiel Brazil <parent>
  • 40.
    Inside a Scope:Nesting place Germany { person Sven visited Brazil place Kiel } ! place Brazil Kiel getScope(personSven, referenceVisited) <returns> Germany Germany.Kiel Brazil <parent> Delegate to parent scope if name not found.
  • 41.
    Inside a Scope:Name Shadowing place Kiel { person Sven visited Brazil place Brazil } ! place Brazil Brazil getScope(personSven, referenceVisited) <returns> Brazil Kiel Kiel.Brazil <parent>
  • 42.
    Inside a Scope:Name Shadowing place Kiel { person Sven visited Brazil place Brazil } ! place Brazil Brazil getScope(personSven, referenceVisited) <returns> Brazil Kiel Kiel.Brazil <parent>
  • 43.
    Qualified Names • QualifiedNames consist of one or more segments • Qualified Names are expected to be unique in a specific context • Bind IQualifiedNameProvider in your RuntimeModule • Default: Collect values of name attributes of all containers fragment = exporting.QualifiedNamesFragment {}
  • 44.
    Qualified Names inthe Grammar Person: "person" name=ID "visited" // visited=[Place] // bad, defaults to ID // visited=[Place|ID] // bad, ID does not allow “.” visited=[Place|FQN]; // good ! FQN: ID ("." ID)*; • If you extend Xbase.xtext or Xtype.xtext, they have a rule for QualifiedNames • If you defined your own, you may use org.eclipse.xtext.conversion.impl.QualifiedNameValueConverter
  • 45.
    Inside a Scope:Name Qualification place Germany { place Kiel { person Sven visited France.Paris place Brazil } place Hamburg } place France { place Paris } BrazilgetScope(personSven, referenceVisited) <returns> Kiel Kiel.Brazil Hamburg <parent> Germany Germany.Kiel Germany.Kiel.Brazil Germany.Hamburg France France.Paris <parent>
  • 46.
    place Germany { place Kiel { person Sven visited France.Paris place Brazil } place Hamburg } place France { place Paris } Inside a Scope: Name Qualification BrazilgetScope(personSven, referenceVisited) <returns> Kiel Kiel.Brazil Hamburg <parent> Germany Germany.Kiel Germany.Kiel.Brazil Germany.Hamburg France France.Paris <parent>
  • 47.
    Default Implementation forImports fragment = scoping.ImportNamespacesScopingFragment {} Import: "import" importedNamespace=FQNWithWildcard; ! FQNWithWildcard: FQN ("." "*")?; MyLanguageScopeProvider ImportedNamespaceAwareLocalScopeProvider
  • 48.
    Default Implementation forCross-File References fragment = scoping.ImportNamespacesScopingFragment {} MyLanguageScopeProvider ImportedNamespaceAwareLocalScopeProvider DefaultGlobalScopeProvider Index
  • 49.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 50.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 51.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 52.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 53.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 54.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places • Import via FQN • Import via wildcard • Cross-file without import • Imports are filename agnostic • Local names have precedence
  • 55.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places Q: Why did Alex not visit Brazil close to Kiel?
  • 56.
    Cross-File References place SouthAmerica{ place Brazil } place USA { place California } west.places import USA.* import SouthAmerica.Brazil ! place Kiel { person Jan visited California person Sven visited Brazil person Anton visited SouthAmerica.Brazil place Brazil } place Luenen { person Alex visited Brazil } home.places Q: Why did Alex not visit Brazil close to Kiel? A: For context “Lünen” it has the name “Kiel.Brazil”
  • 57.
    Implementing own Scopes(extends AbstractDeclarativeScopeProvider) public class PlacesScopeProvider extends AbstractDeclarativeScopeProvider { ! // method is called reflectively via name convention public IScope scope_Person_visited(Person person, EReference reference) { PlacesModel model = EcoreUtil2.getContainerOfType(person, PlacesModel.class); List<Place> allPlaces = EcoreUtil2.getAllContentsOfType(model, Place.class); return Scopes.scopeFor(allPlaces); } } • need unit tests to guarantee method is called • java.reflect cumbersome during debugging • first parameter (context) may be less specific if parser is in lookahead • don’t forget to register an exception handler, otherwise exceptions are ignored
  • 58.
    Implementing own Scopes(implements IScopeProvider) public class PlacesScopeProvider implements IScopeProvider { ! @Inject // obtain the next ScopeProvider in the hierarchy @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) private IScopeProvider delegate; ! // match references and dispatch to specific implementation public IScope getScope(EObject context, EReference reference) { if (reference == PlacesPackage.Literals.PERSON__VISITED) { PlacesModel model = EcoreUtil2.getContainerOfType(context, PlacesModel.class); return getPersonVisitedScope(model); } return delegate.getScope(context, reference); } ! // create scope for PERSON__VISITED private IScope getPersonVisitedScope(PlacesModel model) { // if we can't create a scope, NEVER return null but return IScope.NULLSCOPE instead if (model == null) return IScope.NULLSCOPE; // build some scope. // This one includes all elements from the same file by their simple names List<Place> allPlaces = EcoreUtil2.getAllContentsOfType(model, Place.class); return Scopes.scopeFor(allPlaces); } }
  • 59.
  • 60.
    Use cases forthe Index • Filename-agnostic resolution of names • Validation of cross-references without loading targets • Computation of affected files during incremental build • Reverse-reference lookup
  • 61.
    Interactions with theIndex Index Builder Scoping <updates, reads> <reads> <reads> <reads>
  • 62.
    Data Structure ofthe Index (simplified) ResourceDescriptions uri : URI importedNames : List<String> ResourceDescription sourceEObjectURI : URI targetEObjectURI : URI ReferenceDescription name : String eObjectFragment: String userData : Map<String, String> EObjectDescription [0..*][0..*] [0..*]
  • 63.
    Index Population incremental Build resourcechange <triggers> 1. Index Exported Names 2. Resolve References and Validate
  • 64.
    Index Population incremental Build resourcechange <triggers> During computation of exported names, you CANNOT resolve references. 1. Index Exported Names 2. Resolve References and Validate
  • 65.
    Customize Indexing ofan Xtext-Language #1 public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() { return MyQualifiedNameProvider.class; } MyLanguageRuntimeModule public interface IQualifiedNameProvider extends Function<EObject, QualifiedName> { ! /** * @return the qualified name for the given object, * <code>null</code> if this {@link IQualifiedNameProvider} is not * responsible or if the given object doesn't have qualified name. */ QualifiedName getFullyQualifiedName(EObject obj); }
  • 66.
    Customize Indexing ofan Xtext-Language #2 @ImplementedBy(DefaultResourceDescriptionStrategy.class) public interface IDefaultResourceDescriptionStrategy { ! /** * Calculates the {@link IEObjectDescription}s for * <code>eObject</code> and passes them to the acceptor. * * @return true if the children of <code>eObject</code> should be traversed. */ boolean createEObjectDescriptions( EObject eObject, IAcceptor<IEObjectDescription> acceptor ); ! /** … */ boolean createReferenceDescriptions( EObject eObject, URI exportedContainerURI, IAcceptor<IReferenceDescription> acceptor ); }
  • 67.
  • 68.
    Best Practices • Donot scope to restrictively… • …rather adapt validation and content assist. • Avoid resolving elements when filtering scopes. • Names should not depend on cross-references.
  • 69.