The ability to reference objects that are defined elsewhere, along with goodies like navigation and code completion, is one of the central features of Xtext. When working on your DSL, it is likely that next to validation this is one of the first places where you want/have to do customisations and the number of questions on that topic in the eclipse forum suggests that it is not trivial to do that. One important reason may be the following. The Xtext philosophy is that as much as possible of the standard behaviour should be provided out of the box. However for cross referencing many components are involved and internally a lot of “magic” happens. Many developers are probably not aware of that fact and think that adapting the scope provider (without really having understood its purpose) is all that ever needs to be done.
It has been said that the documentation on that topic (scoping in particular) is good… once you have understood the topic. And, in my opinion, there is truth in that statement. If you have understood the underlying concepts, the documentation is technically very helpful and contains almost everything you need. The problem is that it lacks an easy to understand overview that allows a beginner to get an idea how the different concepts and framework components actually interact. A series of posts is an attempt to provide such an overview. It will not be published in a “sensible” order. It will not be complete. It will not be (very) specific to a particular Xtext version and will not deal with referencing Java types or Xbase. It will not answer questions on specific implementation details. Its purpose is to give developers a rough overview on the topic, giving some background for understanding the documentation, hints for identifying entry points for language specific customisations and some best practices.
Cross References in a(n enormously simplified!) Nutshell
(This will definitely not be the final version of this paragraph)
In Xtext cross references are established via names. This has a number of consequences, e.g an object that is to be referred to must have a name at all, names are not unique and they do not carry any information on where to look for the object. This means that, if cross referencing is to work out of the box, there must be framework components that deal with all of these aspects. And as Xtext tries to make customisation as simple as possible, there are many components that interact.
The name provider is responsible for attaching a name to an object (note that as soon as an object has a name it is by default globally visible). The scope determines which objects are actually visible from a particular location (as a first approximation think of the scope as a lookup table mapping names to objects). Note that “visible” must not be confused with semantically valid, i.e. an object may well be visible with respect to a reference but not be a valid reference. The scope provider (with the heavy use of many helpers, e.g. the index of exported objects) is responsible for creating the scope for each reference. The linker is responsible for reading the name in the model file and choosing from the scope the object to be cross referenced. If no such object can be found, you will get the “could not resolve cross reference”-Error.
The content assist engine will ask the scope for all potential objects and will suggest the names that would be syntactically valid at the current position.
Posts in this series
- Grammar Aspects
- Scoping Relevant Concepts
- The Out-of-the-box/Usability Trade-off
- Implicit/Default Imports
If you find errors, miss topics, think that the explanations should be improved etc., feel free to leave a comment. If time permits, I will try to incorporate them in this overview.