In short, a file of my DSL is compiled to a java class with a constructor that may receive some dependencies. This java class also has some fields - those are actually added during inferring a model. Each field (JvmField) maps to some ArbitraryTypeEntity entity that is described in the file. These fields are dynamically typed - my DSL should obtain types of these fields from their corresponding initialization expressions (here they are called 'constructor' but the real expression might be just a java method, not necessarily a java constructor).
The problem: I'd like to initialize JvmFields not directly (via 'JvmTypesBuilder#setInitializer' method) but from the other place - constructor of my model. I cannot use XExpression constructor directly in the body of my model constructor (it is shown in 'generated code for not working version of the inferrer' code block). May there be some kind of a workaround for this particular problem - reuse default type computation and initialize a field from the other place?
I've made a sample project to reproduce the case and added a snippet of 'desired generated code'.
The grammar of my language:
grammar org.xtext.stackoverflow.initializer_dsl.InitializeDsl with org.eclipse.xtext.xbase.Xbase
generate initializeDsl "http://www.xtext.org/stackoverflow/initializer_dsl/InitializeDsl"
InitializeModel:
{InitializeModel}
elements+=ArbitraryTypeEntity*;
ArbitraryTypeEntity:
'arbitraty entity' name=ID '=' constructor=XOrExpression ';'?
;
My inferrer (I'd like it to be like this):
/*
* generated by Xtext 2.25.0
*/
package org.xtext.stackoverflow.initializer_dsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.stackoverflow.initializer_dsl.initializeDsl.InitializeModel
import org.eclipse.xtext.common.types.JvmVisibility
import java.util.ArrayList
import java.util.HashMap
class InitializeDslJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
def dispatch void infer(InitializeModel element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass("InitializeModel")) [
for (nestedElement : element.elements) {
members += nestedElement.toField(nestedElement.name, nestedElement.constructor.inferredType) [
final = true
static = false
// don't want to use initializer here as the element will later be initialized in constructor
]
}
members += element.toConstructor [
visibility = JvmVisibility.PUBLIC
// I'd like to use the code of constructor XExpression here but I can't - it is compiled to not what I want
body = '''
«FOR nestedElement : element.elements»
this.«nestedElement.name» = «nestedElement.constructor»;
«ENDFOR»
'''
]
]
}
}
But if use this version of my inferrer (with ArbitraryTypeEntity constructor not directly used as initializer of the field), I would get this generated code:
generated code for not working version of the inferrer :
@SuppressWarnings("all")
public class InitializeModel {
private final Object/* type is 'null' */ entity;
public InitializeModel() {
this.entity = org.eclipse.xtext.xbase.impl.XConstructorCallImplCustom@16098231 (invalidFeatureIssueCode: null, validFeature: false, explicitConstructorCall: true, anonymousClassConstructorCall: false);
}
}
Here the type of entity is lost (of course, I didn't associate it in any way with the expression).
Then, if use the JvmTypeBuilder setInitializer function directly and connect the XExpression to JvmField as a logicalChild, it will, of course, work.
working version of inferrer, but not what I'd like to achieve:
/*
* generated by Xtext 2.25.0
*/
package org.xtext.stackoverflow.initializer_dsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.stackoverflow.initializer_dsl.initializeDsl.InitializeModel
import org.eclipse.xtext.common.types.JvmVisibility
import java.util.ArrayList
import java.util.HashMap
class InitializeDslJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
def dispatch void infer(InitializeModel element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass("InitializeModel")) [
for (nestedElement : element.elements) {
members += nestedElement.toField(nestedElement.name, nestedElement.constructor.inferredType) [
final = true
static = false
initializer = nestedElement.constructor
]
}
members += element.toConstructor [
visibility = JvmVisibility.PUBLIC
// now no body is set
// body = '''
// «FOR nestedElement : element.elements»
// this.«nestedElement.name» = «nestedElement.constructor»;
// «ENDFOR»
// '''
]
]
}
}
generated code with the working inferrer version:
import java.util.ArrayList;
@SuppressWarnings("all")
public class InitializeModel {
private final ArrayList<String> entity = new ArrayList<String>();
}
but what I'd like to have is this desired generated code :
import java.util.ArrayList;
@SuppressWarnings("all")
public class InitializeModel {
private final ArrayList<String> entity;
public InitializeModel() {
this.entity = new ArrayList<String>();
}
}
A possible solution that I thought of is:
1. Modify the compiler to not generate the initializing part of code for fields that are mapped to entities of ArbitraryTypeEntity type, so the fields will only be declared
2. Set the initializer - but use a somehow overridden version of XExpression
3. Use in the model constructor body this 'somehow overridden version of XExpression that could be appended to ITreeAppendable of the constructor body.
I could possibly imagine what to do for the first step, but steps 2 and 3 seem to me not realizable for now.