xtextxbase

Grammatically restricted JVMModelInferrer inheritence with Xtext and XBase


The following canonical XBase entities grammar (from "Implementing Domain-Specific Languages with Xtext and Xtend" Bettini) permits entities to extend any Java class. As the commented line indicates, I would like to grammatically force entities to only inherit from entities.

grammar org.example.xbase.entities.Entities with org.eclipse.xtext.xbase.Xbase

generate entities "http://www.example.org/xbase/entities/Entities"

Model:
    importSection=XImportSection?
    entities+=Entity*;

Entity:
    'entity' name=ID ('extends' superType=JvmParameterizedTypeReference)? '{'
//  'entity' name=ID ('extends' superType=[Entity|QualifiedName])? '{'
        attributes += Attribute*
        constructors+=Constructor*
        operations += Operation*
    '}';

Attribute:
    'attr' (type=JvmTypeReference)? name=ID ('=' initexpression=XExpression)? ';';

Operation:
    'op' (type=JvmTypeReference)? name=ID 
    '(' (params+=FullJvmFormalParameter (',' params+=FullJvmFormalParameter)*)? ')' 
        body=XBlockExpression;

Constructor:    'new'  
    '(' (params+=FullJvmFormalParameter (',' params+=FullJvmFormalParameter)*)? ')' 
        body=XBlockExpression;

Here is a working JVMModelInferrer for the model above, again where the commented line (and extra method) reflect my intention.

package org.example.xbase.entities.jvmmodel

import com.google.inject.Inject
import org.eclipse.xtext.common.types.JvmTypeReference
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.example.xbase.entities.entities.Entity

class EntitiesJvmModelInferrer extends AbstractModelInferrer {

    @Inject extension JvmTypesBuilder
    @Inject extension IQualifiedNameProvider

    def dispatch void infer(Entity entity, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
        acceptor.accept(entity.toClass("entities." + entity.name)) [
            documentation = entity.documentation
            if (entity.superType !== null) {
                superTypes += entity.superType.cloneWithProxies
                //superTypes += entity.superType.jvmTypeReference.cloneWithProxies
            }

            entity.attributes.forEach [ a |
                val type = a.type ?: a.initexpression?.inferredType
                members += a.toField(a.name, type) [
                    documentation = a.documentation
                    if (a.initexpression != null)
                        initializer = a.initexpression
                ]
                members += a.toGetter(a.name, type)
                members += a.toSetter(a.name, type)
            ]
            entity.operations.forEach [ op |
                members += op.toMethod(op.name, op.type ?: inferredType) [
                    documentation = op.documentation
                    for (p : op.params) {
                        parameters += p.toParameter(p.name, p.parameterType)
                    }
                    body = op.body
                ]
            ]
            entity.constructors.forEach [ con |
                members += entity.toConstructor [
                    for (p : con.params) {
                        parameters += p.toParameter(p.name, p.parameterType)
                    }
                    body = con.body
                ]
            ]
        ]
    }

    def JvmTypeReference getJvmTypeReference(Entity e) {
        e.toClass(e.fullyQualifiedName).typeRef
    }

}

The following simple instance parses and infers perfectly (with the comments in place).

entity A {
    attr String y;

    new(String y) {
        this.y=y        
    }

}    

entity B extends A {
    new() {
        super("Hello World!")
    }
} 

If, however, I uncomment (and comment in the corresponding line above) both the grammar and the inferrer (and regenerate), the above instance no longer parses. The message is "The method super(String) is undefined".

I understand how to leave the inheritance "loose" and restrict using validators, etc., but would far prefer to strongly type this into the model.

I am lost as to how to solve this, as I am not sure where things are breaking, given the role of XBase and the JvmModelInferrer. A pointer (or reference) would suffice.

[... I am able to implement all the scoping issues for a non-xbase version of this grammar ...]


Solution

  • this won't work. you either have to leave the grammar as is and customize proposal provider and validation. or you have to use "f.q.n.o.y.Entity".typeRef. You can use NodeModelUtils to read the FQN or try something like ("entities."+entity.superType.name).typeRef