From 49d6e953fccc6a9e82aba168bdd2cfc1982bd872 Mon Sep 17 00:00:00 2001 From: Rubin Yoo Date: Wed, 2 Jul 2025 09:34:31 -0700 Subject: [PATCH] disable field init through the scope parameter --- ast/src/main/kotlin/motif/ast/IrAnnotation.kt | 2 + .../motif/ast/compiler/CompilerAnnotation.kt | 3 + .../motif/compiler/JavaCodeGenerator.kt | 65 ++++++++++++---- .../motif/compiler/KotlinCodeGenerator.kt | 74 +++++++++++++------ .../main/kotlin/motif/compiler/ScopeImpl.kt | 1 + .../kotlin/motif/compiler/ScopeImplFactory.kt | 5 ++ gradle.properties | 4 +- .../motif/ast/intellij/IntelliJAnnotation.kt | 4 + lib/src/main/java/motif/Scope.java | 8 +- models/src/main/kotlin/motif/models/Scope.kt | 8 +- .../src/main/java/motif/sample/MainScope.kt | 2 +- .../lib/bottom_header/BottomHeaderScope.java | 2 +- samples/sample/build.gradle | 5 ++ .../app/bottom_sheet/BottomSheetScope.java | 2 +- .../sample/app/photo_grid/PhotoGridScope.java | 2 +- .../sample/app/photo_list/PhotoListScope.java | 2 +- .../java/motif/sample/app/root/RootScope.java | 2 +- .../GRAPH.txt | 42 +++++++++++ .../KT008_use_null_field_init_kotlin/Scope.kt | 44 +++++++++++ .../Test.java | 29 ++++++++ .../T077_use_null_field_init_java/GRAPH.txt | 42 +++++++++++ .../T077_use_null_field_init_java/Scope.java | 46 ++++++++++++ .../T077_use_null_field_init_java/Test.java | 29 ++++++++ 23 files changed, 372 insertions(+), 51 deletions(-) create mode 100644 tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/GRAPH.txt create mode 100644 tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Scope.kt create mode 100644 tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Test.java create mode 100644 tests/src/main/java/testcases/T077_use_null_field_init_java/GRAPH.txt create mode 100644 tests/src/main/java/testcases/T077_use_null_field_init_java/Scope.java create mode 100644 tests/src/main/java/testcases/T077_use_null_field_init_java/Test.java diff --git a/ast/src/main/kotlin/motif/ast/IrAnnotation.kt b/ast/src/main/kotlin/motif/ast/IrAnnotation.kt index acb46285..934869d4 100644 --- a/ast/src/main/kotlin/motif/ast/IrAnnotation.kt +++ b/ast/src/main/kotlin/motif/ast/IrAnnotation.kt @@ -25,5 +25,7 @@ interface IrAnnotation : IrEquivalence { val members: List + val annotationValueMap: Map + fun matchesClass(annotationClass: KClass): Boolean } diff --git a/compiler/ast/src/main/kotlin/motif/ast/compiler/CompilerAnnotation.kt b/compiler/ast/src/main/kotlin/motif/ast/compiler/CompilerAnnotation.kt index 573a571c..ff3f2c6b 100644 --- a/compiler/ast/src/main/kotlin/motif/ast/compiler/CompilerAnnotation.kt +++ b/compiler/ast/src/main/kotlin/motif/ast/compiler/CompilerAnnotation.kt @@ -37,6 +37,9 @@ class CompilerAnnotation(val env: XProcessingEnv, val mirror: XAnnotation) : IrA override val type: IrType = CompilerType(env, mirror.type) + override val annotationValueMap: Map + get() = mirror.annotationValues.associate { it.name to it.value } + override val members: List by lazy { val annotationMethods = mirror.type.typeElement?.getDeclaredMethods().orEmpty() mirror.annotationValues.map { annotationValue -> diff --git a/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt b/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt index 640e9dbb..739821fe 100644 --- a/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt +++ b/compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt @@ -46,13 +46,13 @@ object JavaCodeGenerator { addSuperinterface(superClassName.j) objectsField?.let { addField(it.spec()) } addField(dependenciesField.spec()) - cacheFields.forEach { addField(it.spec()) } + cacheFields.forEach { addField(it.spec(useNullFieldInitialization)) } addMethod(constructor.spec()) alternateConstructor?.let { addMethod(it.spec()) } accessMethodImpls.forEach { addMethod(it.spec()) } childMethodImpls.forEach { addMethod(it.spec()) } addMethod(scopeProviderMethod.spec()) - factoryProviderMethods.forEach { addMethods(it.specs()) } + factoryProviderMethods.forEach { addMethods(it.specs(useNullFieldInitialization)) } dependencyProviderMethods.forEach { addMethod(it.spec()) } dependencies?.let { addType(it.spec()) } objectsImpl?.let { addType(it.spec()) } @@ -80,10 +80,14 @@ object JavaCodeGenerator { private fun DependenciesField.spec(): FieldSpec = FieldSpec.builder(dependenciesClassName.j, name, Modifier.PRIVATE, Modifier.FINAL).build() - private fun CacheField.spec(): FieldSpec = - FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE) - .initializer("\$T.NONE", None::class.java) - .build() + private fun CacheField.spec(useNullFieldInitialization: Boolean): FieldSpec = + if (useNullFieldInitialization) { + FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE).build() + } else { + FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE) + .initializer("\$T.NONE", None::class.java) + .build() + } private fun Constructor.spec(): MethodSpec = MethodSpec.constructorBuilder() @@ -164,30 +168,59 @@ object JavaCodeGenerator { private fun ScopeProviderMethod.spec(): MethodSpec = MethodSpec.methodBuilder(name).returns(scopeClassName.j).addStatement("return this").build() - private fun FactoryProviderMethod.specs(): List { + private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List { val primarySpec = - MethodSpec.methodBuilder(name).returns(returnTypeName.j).addStatement(body.spec()).build() + MethodSpec.methodBuilder(name) + .returns(returnTypeName.j) + .addStatement(body.spec(useNullFieldInitialization)) + .build() val spreadSpecs = spreadProviderMethods.map { it.spec() } return listOf(primarySpec) + spreadSpecs } - private fun FactoryProviderMethodBody.spec(): CodeBlock = + private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock = when (this) { - is FactoryProviderMethodBody.Cached -> spec() + is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization) is FactoryProviderMethodBody.Uncached -> spec() } - private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock = - CodeBlock.builder() - .beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java) + private fun FactoryProviderMethodBody.Cached.spec( + useNullFieldInitialization: Boolean, + ): CodeBlock { + if (useNullFieldInitialization) { + val localFieldName = "_$cacheFieldName" + return CodeBlock.builder() + // Using a local variable reduces atomic read overhead + .add("Object $localFieldName = \$N;\n", cacheFieldName) + .beginControlFlow("if (\$N == null)", localFieldName) .beginControlFlow("synchronized (this)") - .beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java) - .add("\$N = \$L;", cacheFieldName, instantiation.spec()) + .beginControlFlow("if (\$N == null)", cacheFieldName) + .add("\$N = \$L;\n", localFieldName, instantiation.spec()) + .beginControlFlow("if (\$N == null)", localFieldName) + .add( + "throw new \$T(\$S);\n", + NullPointerException::class.java, + "Factory method cannot return null", + ) + .endControlFlow() + .add("\$N = \$L;\n", cacheFieldName, localFieldName) .endControlFlow() .endControlFlow() .endControlFlow() - .add("return (\$T) \$N", returnTypeName.j, cacheFieldName) + .add("return (\$T) \$N", returnTypeName.j, localFieldName) .build() + } + return CodeBlock.builder() + .beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java) + .beginControlFlow("synchronized (this)") + .beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java) + .add("\$N = \$L;", cacheFieldName, instantiation.spec()) + .endControlFlow() + .endControlFlow() + .endControlFlow() + .add("return (\$T) \$N", returnTypeName.j, cacheFieldName) + .build() + } private fun FactoryProviderMethodBody.Uncached.spec(): CodeBlock = CodeBlock.of("return \$L", instantiation.spec()) diff --git a/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt b/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt index 82f00dec..fe0f205b 100644 --- a/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt +++ b/compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt @@ -27,6 +27,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview import com.squareup.kotlinpoet.javapoet.toKClassName import motif.internal.None @@ -48,7 +49,7 @@ object KotlinCodeGenerator { addSuperinterface(superClassName.kt) objectsField?.let { addProperty(it.spec()) } addProperty(dependenciesField.spec()) - cacheFields.forEach { addProperty(it.spec()) } + cacheFields.forEach { addProperty(it.spec(useNullFieldInitialization)) } primaryConstructor(constructor.spec()) alternateConstructor?.let { addFunction(it.spec()) } accessMethodImpls @@ -59,7 +60,7 @@ object KotlinCodeGenerator { .forEach { addProperty(it.propSpec()) } childMethodImpls.forEach { addFunction(it.spec()) } addFunction(scopeProviderMethod.spec()) - factoryProviderMethods.forEach { addFunctions(it.specs()) } + factoryProviderMethods.forEach { addFunctions(it.specs(useNullFieldInitialization)) } dependencyProviderMethods.forEach { addFunction(it.spec()) } dependencies?.let { addType(it.spec()) } objectsImpl?.let { addType(it.spec()) } @@ -96,12 +97,20 @@ object KotlinCodeGenerator { .initializer(name) .build() - private fun CacheField.spec(): PropertySpec = - PropertySpec.builder(name, Any::class, KModifier.PRIVATE) - .mutable(true) - .addAnnotation(Volatile::class) - .initializer("%T.NONE", None::class) - .build() + private fun CacheField.spec(useNullFieldInitialization: Boolean): PropertySpec = + if (useNullFieldInitialization) { + PropertySpec.builder(name, Any::class.asTypeName().copy(true), KModifier.PRIVATE) + .mutable(true) + .addAnnotation(Volatile::class) + .initializer("null") + .build() + } else { + PropertySpec.builder(name, Any::class, KModifier.PRIVATE) + .mutable(true) + .addAnnotation(Volatile::class) + .initializer("%T.NONE", None::class) + .build() + } private fun Constructor.spec(): FunSpec = FunSpec.constructorBuilder() @@ -194,34 +203,55 @@ object KotlinCodeGenerator { .addStatement("return this") .build() - private fun FactoryProviderMethod.specs(): List { + private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List { val primarySpec = FunSpec.builder(name) .addModifiers(KModifier.INTERNAL) .returns(returnTypeName.reloadedForTypeArgs(env)) - .addCode(body.spec()) + .addCode(body.spec(useNullFieldInitialization)) .build() val spreadSpecs = spreadProviderMethods.map { it.spec() } return listOf(primarySpec) + spreadSpecs } - private fun FactoryProviderMethodBody.spec(): CodeBlock = + private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock = when (this) { - is FactoryProviderMethodBody.Cached -> spec() + is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization) is FactoryProviderMethodBody.Uncached -> spec() } - private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock = - CodeBlock.builder() - .beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class) - .beginControlFlow("synchronized (this)") - .beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class) - .addStatement("%N=%L", cacheFieldName, instantiation.spec()) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env)) + private fun FactoryProviderMethodBody.Cached.spec( + useNullFieldInitialization: Boolean, + ): CodeBlock { + if (useNullFieldInitialization) { + val localFieldName = "_$cacheFieldName" + val codeBlockBuilder = + CodeBlock.builder() + // Using a local variable reduces atomic read overhead + .addStatement("var $localFieldName = %N;\n", cacheFieldName) + .beginControlFlow("if (%N == null)", localFieldName) + .beginControlFlow("synchronized (this)") + .beginControlFlow("if (%N == null)", cacheFieldName) + .addStatement("%N = %L", localFieldName, instantiation.spec()) + .addStatement("%N = %N", cacheFieldName, localFieldName) + .endControlFlow() + .endControlFlow() + .endControlFlow() + return codeBlockBuilder + .add("return ( %N as %T )", localFieldName, returnTypeName.reloadedForTypeArgs(env)) .build() + } + return CodeBlock.builder() + .beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class) + .beginControlFlow("synchronized (this)") + .beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class) + .addStatement("%N=%L", cacheFieldName, instantiation.spec()) + .endControlFlow() + .endControlFlow() + .endControlFlow() + .add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env)) + .build() + } private fun motif.compiler.TypeName.reloadedForTypeArgs(env: XProcessingEnv): TypeName = if (kt is ParameterizedTypeName) { diff --git a/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt b/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt index 11e924c8..499e9c0b 100644 --- a/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt +++ b/compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt @@ -35,6 +35,7 @@ import motif.ast.compiler.CompilerMethod * implementations. */ class ScopeImpl( + val useNullFieldInitialization: Boolean, val className: ClassName, val superClassName: ClassName, val internalScope: Boolean, diff --git a/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt b/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt index c56aa5ed..f8049db9 100644 --- a/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt +++ b/compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt @@ -68,6 +68,10 @@ private constructor( fun create(): ScopeImpl { val isInternal = (scope.clazz as? CompilerClass)?.isInternal() ?: false return ScopeImpl( + (scope.clazz.annotations + .find { it.className == motif.Scope::class.java.name }!! + .annotationValueMap[SCOPE_ANNOTATION_FIELD_USE_NULL] + as? Boolean) ?: false, scope.implClassName, scope.typeName, isInternal, @@ -429,6 +433,7 @@ private constructor( private const val OBJECTS_FIELD_NAME = "objects" private const val DEPENDENCIES_FIELD_NAME = "dependencies" + private const val SCOPE_ANNOTATION_FIELD_USE_NULL = "useNullFieldInitialization" fun create(env: XProcessingEnv, graph: ResolvedGraph): List = ScopeImplFactory(env, graph).create() diff --git a/gradle.properties b/gradle.properties index 02c2c588..211fa6d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ # limitations under the License. # GROUP=com.uber.motif -VERSION_NAME=0.4.0-alpha07-SNAPSHOT +VERSION_NAME=0.4.0-alpha08-SNAPSHOT POM_DESCRIPTION=Simple DI API for Android / Java. POM_URL=https://github.com/uber/motif/ POM_SCM_URL=https://github.com/uber/motif/ @@ -33,4 +33,4 @@ android.useAndroidX=true android.enableJetifier=true # https://github.com/Kotlin/dokka/issues/1405 -org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g +org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g \ No newline at end of file diff --git a/intellij/ast/src/main/kotlin/motif/ast/intellij/IntelliJAnnotation.kt b/intellij/ast/src/main/kotlin/motif/ast/intellij/IntelliJAnnotation.kt index 03c95c5c..f726b573 100644 --- a/intellij/ast/src/main/kotlin/motif/ast/intellij/IntelliJAnnotation.kt +++ b/intellij/ast/src/main/kotlin/motif/ast/intellij/IntelliJAnnotation.kt @@ -25,6 +25,7 @@ import com.intellij.psi.PsiElementFactory import com.intellij.psi.PsiField import com.intellij.psi.PsiReferenceExpression import com.intellij.psi.PsiSubstitutor +import java.util.Collections import kotlin.jvm.java import kotlin.jvm.javaClass import kotlin.reflect.KClass @@ -62,6 +63,9 @@ class IntelliJAnnotation(private val project: Project, private val psiAnnotation override fun matchesClass(annotationClass: KClass): Boolean = psiAnnotation.qualifiedName == annotationClass.java.name + override val annotationValueMap: Map + get() = Collections.emptyMap() + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/lib/src/main/java/motif/Scope.java b/lib/src/main/java/motif/Scope.java index 1a4eab4f..489bf148 100644 --- a/lib/src/main/java/motif/Scope.java +++ b/lib/src/main/java/motif/Scope.java @@ -15,4 +15,10 @@ */ package motif; -public @interface Scope {} +public @interface Scope { + /** + * @return on false, the field will be initialized with [None.NONE]. Otherwise, null & + * [Initialized.INITIALIZED] will be used to skip the field initialization. + */ + boolean useNullFieldInitialization() default false; +} diff --git a/models/src/main/kotlin/motif/models/Scope.kt b/models/src/main/kotlin/motif/models/Scope.kt index b4cf68a5..71c78c92 100644 --- a/models/src/main/kotlin/motif/models/Scope.kt +++ b/models/src/main/kotlin/motif/models/Scope.kt @@ -19,8 +19,7 @@ import motif.ast.IrClass import motif.ast.IrType /** [Wiki](https://github.com/uber/motif/wiki#scope) */ -sealed class Scope(val clazz: IrClass) { - +sealed class Scope(val useNullFieldInitialization: Boolean, val clazz: IrClass) { val source by lazy { ScopeSource(this) } val simpleName: String by lazy { clazz.simpleName } val qualifiedName: String by lazy { clazz.qualifiedName } @@ -38,7 +37,7 @@ sealed class Scope(val clazz: IrClass) { } class ErrorScope internal constructor(clazz: IrClass, val parsingError: ParsingError) : - Scope(clazz) { + Scope(useNullFieldInitialization = false, clazz) { override val objects: Objects? = null override val accessMethods: List = emptyList() override val childMethods: List = emptyList() @@ -46,7 +45,8 @@ class ErrorScope internal constructor(clazz: IrClass, val parsingError: ParsingE override val dependencies: Dependencies? = null } -class ValidScope internal constructor(clazz: IrClass) : Scope(clazz) { +class ValidScope internal constructor(clazz: IrClass, useNullFieldInitialization: Boolean = false) : + Scope(useNullFieldInitialization, clazz) { init { if (clazz.kind != IrClass.Kind.INTERFACE) throw ScopeMustBeAnInterface(clazz) diff --git a/samples/sample-kotlin/src/main/java/motif/sample/MainScope.kt b/samples/sample-kotlin/src/main/java/motif/sample/MainScope.kt index e4977ed6..4e6b2ac2 100644 --- a/samples/sample-kotlin/src/main/java/motif/sample/MainScope.kt +++ b/samples/sample-kotlin/src/main/java/motif/sample/MainScope.kt @@ -19,7 +19,7 @@ import javax.inject.Named import motif.Creatable import motif.Scope -@Scope +@Scope(useNullFieldInitialization = true) interface MainScope : Creatable { fun greeter(): Greeter diff --git a/samples/sample-lib/src/main/java/motif/sample/lib/bottom_header/BottomHeaderScope.java b/samples/sample-lib/src/main/java/motif/sample/lib/bottom_header/BottomHeaderScope.java index 03b5aae4..b3b2d869 100644 --- a/samples/sample-lib/src/main/java/motif/sample/lib/bottom_header/BottomHeaderScope.java +++ b/samples/sample-lib/src/main/java/motif/sample/lib/bottom_header/BottomHeaderScope.java @@ -18,7 +18,7 @@ import motif.Scope; import motif.sample.lib.controller.ControllerObjects; -@Scope +@Scope(useNullFieldInitialization = true) public interface BottomHeaderScope { BottomHeaderView view(); diff --git a/samples/sample/build.gradle b/samples/sample/build.gradle index bcaf1e78..8ef8443d 100644 --- a/samples/sample/build.gradle +++ b/samples/sample/build.gradle @@ -11,6 +11,11 @@ android { targetSdkVersion deps.build.targetSdkVersion } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + defaultConfig { minSdkVersion deps.build.minSdkVersion targetSdkVersion deps.build.targetSdkVersion diff --git a/samples/sample/src/main/java/motif/sample/app/bottom_sheet/BottomSheetScope.java b/samples/sample/src/main/java/motif/sample/app/bottom_sheet/BottomSheetScope.java index 3f3465d7..986c0699 100644 --- a/samples/sample/src/main/java/motif/sample/app/bottom_sheet/BottomSheetScope.java +++ b/samples/sample/src/main/java/motif/sample/app/bottom_sheet/BottomSheetScope.java @@ -22,7 +22,7 @@ import motif.sample.lib.bottom_header.BottomHeaderScope; import motif.sample.lib.controller.ControllerObjects; -@Scope +@Scope(useNullFieldInitialization = true) public interface BottomSheetScope { BottomSheetView view(); diff --git a/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java b/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java index 404d8a93..88acbdbc 100644 --- a/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java +++ b/samples/sample/src/main/java/motif/sample/app/photo_grid/PhotoGridScope.java @@ -21,7 +21,7 @@ import motif.sample.lib.controller.ControllerObjects; import motif.sample.lib.db.Photo; -@Scope +@Scope(useNullFieldInitialization = true) public interface PhotoGridScope { PhotoGridView view(); diff --git a/samples/sample/src/main/java/motif/sample/app/photo_list/PhotoListScope.java b/samples/sample/src/main/java/motif/sample/app/photo_list/PhotoListScope.java index 7b8936ad..ce22f0e7 100644 --- a/samples/sample/src/main/java/motif/sample/app/photo_list/PhotoListScope.java +++ b/samples/sample/src/main/java/motif/sample/app/photo_list/PhotoListScope.java @@ -21,7 +21,7 @@ import motif.sample.lib.controller.ControllerObjects; import motif.sample.lib.db.Photo; -@Scope +@Scope(useNullFieldInitialization = true) public interface PhotoListScope { PhotoListView view(); diff --git a/samples/sample/src/main/java/motif/sample/app/root/RootScope.java b/samples/sample/src/main/java/motif/sample/app/root/RootScope.java index 02394488..d943ea69 100644 --- a/samples/sample/src/main/java/motif/sample/app/root/RootScope.java +++ b/samples/sample/src/main/java/motif/sample/app/root/RootScope.java @@ -27,7 +27,7 @@ import motif.sample.lib.db.Database; import motif.sample.lib.multiselect.MultiSelector; -@Scope +@Scope(useNullFieldInitialization = true) public interface RootScope { RootView view(); diff --git a/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/GRAPH.txt b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/GRAPH.txt new file mode 100644 index 00000000..abf4850c --- /dev/null +++ b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/GRAPH.txt @@ -0,0 +1,42 @@ +######################################################################## +# # +# This file is auto-generated by running the Motif compiler tests and # +# serves a as validation of graph correctness. IntelliJ plugin tests # +# also rely on this file to ensure that the plugin graph understanding # +# is equivalent to the compiler's. # +# # +# - Do not edit manually. # +# - Commit changes to source control. # +# - Since this file is autogenerated, code review changes carefully to # +# ensure correctness. # +# # +######################################################################## + + ------- +| Scope | + ------- + + ==== Required ==== + + ==== Provides ==== + + ---- int | Objects.fooInt ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooInt() + + ---- Object | Objects.fooObject ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooObject() + + ---- String | Objects.fooString ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooString() + + ---- Scope | implicit ---- + [ Required ] + [ Consumed By ] + + diff --git a/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Scope.kt b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Scope.kt new file mode 100644 index 00000000..15e32c55 --- /dev/null +++ b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Scope.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2019 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package testcases.KT008_use_null_field_init_kotlin + +import motif.Creatable + +@motif.Scope(useNullFieldInitialization = true) +interface Scope : Creatable { + fun fooObject(): Any + + fun fooInt(): Int + + fun fooString(): String + + @motif.Objects + abstract class Objects { + fun fooObject(): Any { + return Any() + } + + fun fooInt(): Int { + return 3 + } + + fun fooString(): String { + return "fooString" + } + } + + interface Dependencies +} \ No newline at end of file diff --git a/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Test.java b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Test.java new file mode 100644 index 00000000..1cb8bf80 --- /dev/null +++ b/tests/src/main/java/testcases/KT008_use_null_field_init_kotlin/Test.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2019 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package testcases.KT008_use_null_field_init_kotlin; + +import static com.google.common.truth.Truth.assertThat; + +public class Test { + + public static void run() { + Scope scope = new ScopeImpl(); + assertThat(scope.fooString()).isEqualTo("fooString"); + assertThat(scope.fooInt()).isEqualTo(3); + assertThat(scope.fooObject()).isNotNull(); + assertThat(scope.fooObject()).isEqualTo(scope.fooObject()); + } +} diff --git a/tests/src/main/java/testcases/T077_use_null_field_init_java/GRAPH.txt b/tests/src/main/java/testcases/T077_use_null_field_init_java/GRAPH.txt new file mode 100644 index 00000000..abf4850c --- /dev/null +++ b/tests/src/main/java/testcases/T077_use_null_field_init_java/GRAPH.txt @@ -0,0 +1,42 @@ +######################################################################## +# # +# This file is auto-generated by running the Motif compiler tests and # +# serves a as validation of graph correctness. IntelliJ plugin tests # +# also rely on this file to ensure that the plugin graph understanding # +# is equivalent to the compiler's. # +# # +# - Do not edit manually. # +# - Commit changes to source control. # +# - Since this file is autogenerated, code review changes carefully to # +# ensure correctness. # +# # +######################################################################## + + ------- +| Scope | + ------- + + ==== Required ==== + + ==== Provides ==== + + ---- int | Objects.fooInt ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooInt() + + ---- Object | Objects.fooObject ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooObject() + + ---- String | Objects.fooString ---- + [ Required ] + [ Consumed By ] + * Scope | Scope.fooString() + + ---- Scope | implicit ---- + [ Required ] + [ Consumed By ] + + diff --git a/tests/src/main/java/testcases/T077_use_null_field_init_java/Scope.java b/tests/src/main/java/testcases/T077_use_null_field_init_java/Scope.java new file mode 100644 index 00000000..535ddd56 --- /dev/null +++ b/tests/src/main/java/testcases/T077_use_null_field_init_java/Scope.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2019 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package testcases.T077_use_null_field_init_java; + +import motif.Creatable; + +@motif.Scope(useNullFieldInitialization = true) +public interface Scope extends Creatable { + + Object fooObject(); + + int fooInt(); + + String fooString(); + + @motif.Objects + class Objects { + + Object fooObject() { + return new Object(); + } + + int fooInt() { + return 3; + } + + String fooString() { + return "fooString"; + } + } + + interface Dependencies {} +} \ No newline at end of file diff --git a/tests/src/main/java/testcases/T077_use_null_field_init_java/Test.java b/tests/src/main/java/testcases/T077_use_null_field_init_java/Test.java new file mode 100644 index 00000000..1601cd1b --- /dev/null +++ b/tests/src/main/java/testcases/T077_use_null_field_init_java/Test.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2019 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package testcases.T077_use_null_field_init_java; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +public class Test { + + public static void run() { + Scope scope = new ScopeImpl(); + assertThat(scope.fooString()).isEqualTo("fooString"); + assertThat(scope.fooInt()).isEqualTo(3); + assertThat(scope.fooObject()).isNotNull(); + assertThat(scope.fooObject()).isEqualTo(scope.fooObject()); + } +}