Configuring local and global scope providers at the same time
This post is about how to take control of configuration of the local and global scope providers through a custom scoping fragment.
In the .mwe2 workflow file (I’m assuming you’re on Xtext 1.0.1 and Eclipse Helios) the scoping is configured through an appropriate Xtext generator fragment. Xtext is shipped with an org.eclipse.xtext.generator.scoping.AbstractScopingFragment support class and two implementations (in the same Java package): ImportNamespacesScopingFragment and ImportURIScopingFragment.
AbstractScopingFragment binds the custom scope provider implementation MyDslScopeProvider (which inherits from org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider, by default) to the runtime (Guice module) configuration and provides two abstract methods to also declare which classes you want to bind to the runtime for the global scope provider and a delegate or ‘fall-back’ local scope provider which is injected into the custom scope provider (in the field AbstractDeclarativeScopeProvider#delegate). This scoping delegate is invoked in case the custom scope provider (as long as that still inherits from AbstractDeclarativeScopeProvider, which would typically be the case) happens to return a null scope. (Note that ‘local’ means nothing more than ‘confined to the current Resource‘ with ‘global’ being the opposite.)
The following figure attempts to sum up the default situation which you get out-of-the-box after creating an Xtext project (with the familiar language name ‘MyDsl’, in this case) and running the MWE2 workflow:
The MWE2 workflow file (in the 1st editor) specifies the ImportNamespacesScopingFragment as (only) scope fragment. The source for the fragment (2nd editor) shows that in this case-sensitive case, the delegate local scope provider is to be an instance of ImportedNamespaceAwareLocalScopeProvider and the global scope provider is to be an instance of DefaultGlobalScopeProvider. Indeed, the 3rd editor shows that IScopeProvider is bound to the custom local scope provider, the delegate field (which is appropriately tagged using a Guice Named annotation) to ImportedNamespaceAwareLocalScopeProvider and IGlobalScopeProvider to the DefaultGlobalScopeProvider class.
As you already can guess from the fragments’ source in the example above, the two default implementations of AbstractScopingFragment mentioned above both declare both a global scope provider class as well as a local delegate scope provider class, so when configuring both fragments in the DSL generator workflow, the bindings will override each other and order is important. This is particularly annoying in case you want both the namespace-based imports as well the global URI imports functionality -after all these don’t necessarily conflict. Also, you have to configure at least one scoping fragment in order to have the custom scope provider bound, which may mean that your DSL could have unwanted scoping properties.
The solution is simply to roll your own AbstractScopingFragment implementation and configure exactly what you need. Note that this is only possible since Xtext 1.0.1 aka SR1 since before that the org.eclipse.xtext.generator.scoping package this class resides in, was not exported from the plugin, so this class and the Xpand and Xtext files it needs are not accessible to the class loader. With any implementation of AbstractScopingFragment should come an Xpand file with the exact same name as the implementing class’ name and residing in the same package. This Xpand file allows you to execute additional templates, either as separate files or as “add-ins”. We just need to execute the one for the custom Java scope provider, so we can simply copy the ImportURIScopingFragment.xpt file from within the plugin and rename it appropriately. The following screenshot sums things up:
After modifying the Xtext generator workflow and running it again, the workflow file and binding situation now look as follows:
This is indeed what we wanted. Note that the two standard scoping fragments will usually suffice, since you can override basically anything in the custom scope provider. But, for full-blown language development as well as for language modularization it sure comes in handy to have ultimate control of the scoping architecture.