Xtext cross references and scoping - an overview (Part 2)
The Out-of-the-box/Usability Trade-off
Scopes are used by a number of components (most notably linking and content assist). This often tempts developers to implement the scope provider such that only objects that are indeed allowed to be linked are put in the scope. After all, then validation and code completion need not be adapted at all (hooray! everything works out of the box), as only valid links can be established and only valid links are proposed. However, scopes are not about validity, they are about visibility.
An analogous problem is grammar design itself. There may be a restriction that entity names have to start with an upper case letter. You can enforce that within the grammar, e.g. by defining a corresponding terminal rule. However, this will make the grammar error prone and the error messages for the user will not be very helpful. There might be a restriction that the value of a certain feature may only be between 5 and 49. Go ahead and put that into the grammar (it’s possible after all), you will have plenty of fun (in particular with other integer value features). Obviously, there are often several ways to achieve something and choices must be made.
The decision what to put into the scope should be made very carefully. There is no simple rule to be followed. Here are a few guide lines, I find sensible:
Single user language
If you develop a language/IDE for yourself only, take the path simplest for you. You know the language, you know the intended meaning, you (should) know why a cross reference could not be resolved. There is nobody who can argue that validity and visibility are not the same.
Multi user language
If you develop a language/IDE for use by somebody else, think thoroughly about usability. Usually, the user should be provided with a message specifically why a certain cross reference is invalid
- you cannot extend entity X because it is final
- the type of feature Y must implement a method Z and the type you try to use does not do so
Of course this is a lot of work. You may still have to adapt scoping (reduce the number of visible objects, but not too much), adapt validation (check semantic constraints that must be satisfied), adapt code completion (suggest only valid objects). However, you can provide more meaningful error messages and specific quick fixes (great! still more work). But don’t underestimate how stupid your editor may look.
variable x; variable y=x+3; //could not resolve reference to x
The developer may know that only initialized variables can be linked (which is why only those were put in the scope) but the user’s reaction will be something like “st*&%/$/”Â§)…, look at the line above”.
“Follow up references”
A use case, where a drastic narrowing of the scope is almost always OK, is a reference that “follows” another one. Take feature calls as an example.
If you already established a link to myType and now only properties of that type are valid, it makes no sense, having all other possible properties on the scope. On seeing a “could not resolve reference” error, the user can navigate to myType and see that there is indeed no such property.