Skip to content

Commit b226d94

Browse files
authored
Merge pull request #530 from modelix/fix/modeql-write-support-manual-casting
MODELIX-776 Automatically cast MonoSteps for newly created nodes
2 parents 57bc7f2 + 9a2ae7c commit b226d94

File tree

3 files changed

+84
-21
lines changed

3 files changed

+84
-21
lines changed

model-api-gen-gradle-test/kotlin-generation/src/test/kotlin/org/modelix/modelql/typed/TypedModelQLTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,15 @@ abstract class TypedModelQLTest {
360360

361361
assertTrue { actual.instanceOf(C_XmlDocument) }
362362
}
363+
364+
@Test
365+
fun `write operations return typed nodes`() = runTest { client ->
366+
val name = client.query { root ->
367+
root.children("classes").ofConcept(C_ClassConcept)
368+
.first()
369+
.addToMember(C_StaticMethodDeclaration)
370+
.name
371+
}
372+
assertEquals("", name)
373+
}
363374
}

model-api-gen/src/main/kotlin/org/modelix/metamodel/generator/internal/ModelQLExtensionsGenerator.kt

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import com.squareup.kotlinpoet.ParameterSpec
2323
import com.squareup.kotlinpoet.ParameterizedTypeName
2424
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
2525
import com.squareup.kotlinpoet.PropertySpec
26+
import com.squareup.kotlinpoet.TypeVariableName
2627
import com.squareup.kotlinpoet.asTypeName
27-
import org.modelix.metamodel.ITypedConcept
28+
import org.modelix.metamodel.IConceptOfTypedNode
2829
import org.modelix.metamodel.generator.MetaModelGenerator
2930
import org.modelix.metamodel.generator.NameConfig
3031
import org.modelix.metamodel.generator.ProcessedChildLink
@@ -66,6 +67,9 @@ internal class ModelQLExtensionsGenerator(
6667
is ProcessedChildLink -> {
6768
addChildGetter(feature)
6869
addChildSetter(feature)
70+
if (!feature.type.resolved.abstract) {
71+
addDefaultChildSetter(feature)
72+
}
6973
}
7074

7175
is ProcessedReferenceLink -> {
@@ -80,7 +84,7 @@ internal class ModelQLExtensionsGenerator(
8084

8185
private fun FileSpec.Builder.addReferenceSetter(referenceLink: ProcessedReferenceLink) {
8286
val targetType = referenceLink.type.resolved.nodeWrapperInterfaceType().copy(nullable = referenceLink.optional)
83-
val inputStepType = IMonoStep::class.asTypeName().parameterizedBy(concept.nodeWrapperInterfaceType())
87+
val inputStepType = getSetterReceiverType()
8488

8589
val parameterType = IMonoStep::class.asTypeName().parameterizedBy(targetType)
8690
.let { if (referenceLink.optional) it.copy(nullable = true) else it }
@@ -159,26 +163,23 @@ internal class ModelQLExtensionsGenerator(
159163

160164
private fun FileSpec.Builder.addChildSetter(childLink: ProcessedChildLink) {
161165
val targetType = childLink.type.resolved.nodeWrapperInterfaceType()
162-
val returnType = IMonoStep::class.asTypeName().parameterizedBy(targetType)
163-
val receiverType = IMonoStep::class.asTypeName().parameterizedBy(concept.nodeWrapperInterfaceType())
164-
val conceptParameter = ParameterSpec.builder("concept", ITypedConcept::class.asTypeName()).apply {
165-
if (!childLink.type.resolved.abstract) {
166-
defaultValue("%T", childLink.type.resolved.conceptWrapperInterfaceClass())
167-
}
168-
}.build()
166+
val outType = TypeVariableName("Out", targetType)
167+
val returnType = IMonoStep::class.asTypeName().parameterizedBy(outType)
168+
val receiverType = getSetterReceiverType()
169+
val conceptParameter = ParameterSpec.builder(
170+
name = "concept",
171+
type = IConceptOfTypedNode::class.asTypeName().parameterizedBy(outType),
172+
).build()
169173

170-
val funName = if (childLink.multiple) childLink.adderMethodName() else childLink.setterName()
174+
val funName = getSetterName(childLink)
171175

172176
val funSpec = FunSpec.builder(funName).runBuild {
177+
addTypeVariable(outType)
173178
returns(returnType)
174179
receiver(receiverType)
175180
addParameter(conceptParameter)
176181
if (childLink.multiple) {
177-
val indexParameter = ParameterSpec.builder("index", Int::class.asTypeName())
178-
.defaultValue("-1")
179-
.build()
180-
181-
addParameter(indexParameter)
182+
addIndexParameter()
182183
addStatement(
183184
"return %T.addNewChild(this, %T.%N, index, concept)",
184185
TypedModelQL::class.asTypeName(),
@@ -198,6 +199,55 @@ internal class ModelQLExtensionsGenerator(
198199
addFunction(funSpec)
199200
}
200201

202+
/*
203+
If we use the concept companion object as a default value for the concept parameter,
204+
the kotlin compiler reports a type mismatch since it cannot properly infer the type variable.
205+
That's why we need a separate default setter.
206+
*/
207+
private fun FileSpec.Builder.addDefaultChildSetter(childLink: ProcessedChildLink) {
208+
val targetType = childLink.type.resolved.conceptWrapperInterfaceClass()
209+
val returnType = IMonoStep::class.asTypeName().parameterizedBy(childLink.type.resolved.nodeWrapperInterfaceType())
210+
val receiverType = getSetterReceiverType()
211+
212+
val funName = getSetterName(childLink)
213+
214+
val funSpec = FunSpec.builder(funName).runBuild {
215+
returns(returnType)
216+
receiver(receiverType)
217+
if (childLink.multiple) {
218+
addIndexParameter()
219+
addStatement(
220+
"return %T.addNewChild(this, %T.%N, index, %T)",
221+
TypedModelQL::class.asTypeName(),
222+
concept.conceptObjectType(),
223+
childLink.generatedName,
224+
targetType,
225+
)
226+
} else {
227+
addStatement(
228+
"return %T.setChild(this, %T.%N, %T)",
229+
TypedModelQL::class.asTypeName(),
230+
concept.conceptObjectType(),
231+
childLink.generatedName,
232+
targetType,
233+
)
234+
}
235+
}
236+
237+
addFunction(funSpec)
238+
}
239+
240+
private fun FunSpec.Builder.addIndexParameter() {
241+
val indexParameter = ParameterSpec.builder("index", Int::class.asTypeName())
242+
.defaultValue("-1")
243+
.build()
244+
245+
addParameter(indexParameter)
246+
}
247+
248+
private fun getSetterName(childLink: ProcessedChildLink) =
249+
if (childLink.multiple) childLink.adderMethodName() else childLink.setterName()
250+
201251
private fun FileSpec.Builder.addChildGetter(childLink: ProcessedChildLink) {
202252
val inputStepType = (if (childLink.multiple) IProducingStep::class else IMonoStep::class).asTypeName()
203253
val outputStepType = (if (childLink.multiple) IFluxStep::class else IMonoStep::class).asTypeName()
@@ -225,8 +275,7 @@ internal class ModelQLExtensionsGenerator(
225275
}
226276

227277
private fun FileSpec.Builder.addPropertySetter(property: ProcessedProperty) {
228-
val inputStepType = IMonoStep::class.asTypeName()
229-
.parameterizedBy(concept.nodeWrapperInterfaceType())
278+
val inputStepType = getSetterReceiverType()
230279

231280
val parameterType = IMonoStep::class.asTypeName()
232281
.parameterizedBy(property.asKotlinType(alwaysUseNonNullableProperties))
@@ -246,6 +295,9 @@ internal class ModelQLExtensionsGenerator(
246295
addFunction(setterSpec)
247296
}
248297

298+
private fun getSetterReceiverType() =
299+
IMonoStep::class.asTypeName().parameterizedBy(concept.nodeWrapperInterfaceType())
300+
249301
private fun FileSpec.Builder.addPropertyGetterForStepType(
250302
property: ProcessedProperty,
251303
stepType: ClassName,

modelql-typed/src/commonMain/kotlin/org/modelix/modelql/typed/TypedModelQL.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,15 @@ object TypedModelQL {
144144
return input.untyped().flatMap { it.children(link.untyped().key()) }.typedUnsafe(link.getTypedChildConcept().getInstanceInterface())
145145
}
146146

147-
fun <ParentT : ITypedNode, ChildT : ITypedNode> addNewChild(input: IMonoStep<ParentT>, link: ITypedChildListLink<ChildT>, index: Int = -1, concept: ITypedConcept): IMonoStep<ChildT> {
147+
fun <ParentT : ITypedNode, ChildT : ITypedNode, Out : ChildT> addNewChild(input: IMonoStep<ParentT>, link: ITypedChildListLink<ChildT>, index: Int = -1, concept: IConceptOfTypedNode<Out>): IMonoStep<Out> {
148148
val conceptRef = ConceptReference(concept.untyped().getUID())
149-
return input.untyped().addNewChild(link.untyped(), index, conceptRef).typedUnsafe(link.getTypedChildConcept().getInstanceInterface())
149+
return input.untyped().addNewChild(link.untyped(), index, conceptRef).ofConcept(concept)
150150
}
151151

152-
fun <ParentT : ITypedNode, ChildT : ITypedNode> setChild(input: IMonoStep<ParentT>, link: ITypedSingleChildLink<ChildT>, concept: ITypedConcept): IMonoStep<ChildT> {
152+
fun <ParentT : ITypedNode, ChildT : ITypedNode, Out : ChildT> setChild(input: IMonoStep<ParentT>, link: ITypedSingleChildLink<ChildT>, concept: IConceptOfTypedNode<Out>): IMonoStep<Out> {
153153
val conceptRef = ConceptReference(concept.untyped().getUID())
154154
input.untyped().children(link.untyped().key()).firstOrNull().mapIfNotNull { it.remove() }
155-
return input.untyped().addNewChild(link.untyped(), conceptRef).typedUnsafe(link.getTypedChildConcept().getInstanceInterface())
155+
return input.untyped().addNewChild(link.untyped(), conceptRef).ofConcept(concept)
156156
}
157157

158158
fun <SourceT : ITypedNode, TargetT : ITypedNode> reference(input: IMonoStep<SourceT>, link: ITypedReferenceLink<TargetT>): IMonoStep<TargetT> {

0 commit comments

Comments
 (0)