Skip to content

Commit af3011a

Browse files
rysh88psteiger
authored andcommitted
disable field init through the scope parameter
1 parent 57aec6b commit af3011a

23 files changed

Lines changed: 372 additions & 51 deletions

File tree

ast/src/main/kotlin/motif/ast/IrAnnotation.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ interface IrAnnotation : IrEquivalence {
2525

2626
val members: List<IrMethod>
2727

28+
val annotationValueMap: Map<String, Any?>
29+
2830
fun matchesClass(annotationClass: KClass<out Annotation>): Boolean
2931
}

compiler/ast/src/main/kotlin/motif/ast/compiler/CompilerAnnotation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class CompilerAnnotation(val env: XProcessingEnv, val mirror: XAnnotation) : IrA
3737

3838
override val type: IrType = CompilerType(env, mirror.type)
3939

40+
override val annotationValueMap: Map<String, Any?>
41+
get() = mirror.annotationValues.associate { it.name to it.value }
42+
4043
override val members: List<IrMethod> by lazy {
4144
val annotationMethods = mirror.type.typeElement?.getDeclaredMethods().orEmpty()
4245
mirror.annotationValues.map { annotationValue ->

compiler/src/main/kotlin/motif/compiler/JavaCodeGenerator.kt

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ object JavaCodeGenerator {
4646
addSuperinterface(superClassName.j)
4747
objectsField?.let { addField(it.spec()) }
4848
addField(dependenciesField.spec())
49-
cacheFields.forEach { addField(it.spec()) }
49+
cacheFields.forEach { addField(it.spec(useNullFieldInitialization)) }
5050
addMethod(constructor.spec())
5151
alternateConstructor?.let { addMethod(it.spec()) }
5252
accessMethodImpls.forEach { addMethod(it.spec()) }
5353
childMethodImpls.forEach { addMethod(it.spec()) }
5454
addMethod(scopeProviderMethod.spec())
55-
factoryProviderMethods.forEach { addMethods(it.specs()) }
55+
factoryProviderMethods.forEach { addMethods(it.specs(useNullFieldInitialization)) }
5656
dependencyProviderMethods.forEach { addMethod(it.spec()) }
5757
dependencies?.let { addType(it.spec()) }
5858
objectsImpl?.let { addType(it.spec()) }
@@ -80,10 +80,14 @@ object JavaCodeGenerator {
8080
private fun DependenciesField.spec(): FieldSpec =
8181
FieldSpec.builder(dependenciesClassName.j, name, Modifier.PRIVATE, Modifier.FINAL).build()
8282

83-
private fun CacheField.spec(): FieldSpec =
84-
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE)
85-
.initializer("\$T.NONE", None::class.java)
86-
.build()
83+
private fun CacheField.spec(useNullFieldInitialization: Boolean): FieldSpec =
84+
if (useNullFieldInitialization) {
85+
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE).build()
86+
} else {
87+
FieldSpec.builder(Object::class.java, name, Modifier.PRIVATE, Modifier.VOLATILE)
88+
.initializer("\$T.NONE", None::class.java)
89+
.build()
90+
}
8791

8892
private fun Constructor.spec(): MethodSpec =
8993
MethodSpec.constructorBuilder()
@@ -164,30 +168,59 @@ object JavaCodeGenerator {
164168
private fun ScopeProviderMethod.spec(): MethodSpec =
165169
MethodSpec.methodBuilder(name).returns(scopeClassName.j).addStatement("return this").build()
166170

167-
private fun FactoryProviderMethod.specs(): List<MethodSpec> {
171+
private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List<MethodSpec> {
168172
val primarySpec =
169-
MethodSpec.methodBuilder(name).returns(returnTypeName.j).addStatement(body.spec()).build()
173+
MethodSpec.methodBuilder(name)
174+
.returns(returnTypeName.j)
175+
.addStatement(body.spec(useNullFieldInitialization))
176+
.build()
170177
val spreadSpecs = spreadProviderMethods.map { it.spec() }
171178
return listOf(primarySpec) + spreadSpecs
172179
}
173180

174-
private fun FactoryProviderMethodBody.spec(): CodeBlock =
181+
private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock =
175182
when (this) {
176-
is FactoryProviderMethodBody.Cached -> spec()
183+
is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization)
177184
is FactoryProviderMethodBody.Uncached -> spec()
178185
}
179186

180-
private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock =
181-
CodeBlock.builder()
182-
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
187+
private fun FactoryProviderMethodBody.Cached.spec(
188+
useNullFieldInitialization: Boolean,
189+
): CodeBlock {
190+
if (useNullFieldInitialization) {
191+
val localFieldName = "_$cacheFieldName"
192+
return CodeBlock.builder()
193+
// Using a local variable reduces atomic read overhead
194+
.add("Object $localFieldName = \$N;\n", cacheFieldName)
195+
.beginControlFlow("if (\$N == null)", localFieldName)
183196
.beginControlFlow("synchronized (this)")
184-
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
185-
.add("\$N = \$L;", cacheFieldName, instantiation.spec())
197+
.beginControlFlow("if (\$N == null)", cacheFieldName)
198+
.add("\$N = \$L;\n", localFieldName, instantiation.spec())
199+
.beginControlFlow("if (\$N == null)", localFieldName)
200+
.add(
201+
"throw new \$T(\$S);\n",
202+
NullPointerException::class.java,
203+
"Factory method cannot return null",
204+
)
205+
.endControlFlow()
206+
.add("\$N = \$L;\n", cacheFieldName, localFieldName)
186207
.endControlFlow()
187208
.endControlFlow()
188209
.endControlFlow()
189-
.add("return (\$T) \$N", returnTypeName.j, cacheFieldName)
210+
.add("return (\$T) \$N", returnTypeName.j, localFieldName)
190211
.build()
212+
}
213+
return CodeBlock.builder()
214+
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
215+
.beginControlFlow("synchronized (this)")
216+
.beginControlFlow("if (\$N == \$T.NONE)", cacheFieldName, None::class.java)
217+
.add("\$N = \$L;", cacheFieldName, instantiation.spec())
218+
.endControlFlow()
219+
.endControlFlow()
220+
.endControlFlow()
221+
.add("return (\$T) \$N", returnTypeName.j, cacheFieldName)
222+
.build()
223+
}
191224

192225
private fun FactoryProviderMethodBody.Uncached.spec(): CodeBlock =
193226
CodeBlock.of("return \$L", instantiation.spec())

compiler/src/main/kotlin/motif/compiler/KotlinCodeGenerator.kt

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName
2727
import com.squareup.kotlinpoet.PropertySpec
2828
import com.squareup.kotlinpoet.TypeName
2929
import com.squareup.kotlinpoet.TypeSpec
30+
import com.squareup.kotlinpoet.asTypeName
3031
import com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview
3132
import com.squareup.kotlinpoet.javapoet.toKClassName
3233
import motif.internal.None
@@ -48,7 +49,7 @@ object KotlinCodeGenerator {
4849
addSuperinterface(superClassName.kt)
4950
objectsField?.let { addProperty(it.spec()) }
5051
addProperty(dependenciesField.spec())
51-
cacheFields.forEach { addProperty(it.spec()) }
52+
cacheFields.forEach { addProperty(it.spec(useNullFieldInitialization)) }
5253
primaryConstructor(constructor.spec())
5354
alternateConstructor?.let { addFunction(it.spec()) }
5455
accessMethodImpls
@@ -59,7 +60,7 @@ object KotlinCodeGenerator {
5960
.forEach { addProperty(it.propSpec()) }
6061
childMethodImpls.forEach { addFunction(it.spec()) }
6162
addFunction(scopeProviderMethod.spec())
62-
factoryProviderMethods.forEach { addFunctions(it.specs()) }
63+
factoryProviderMethods.forEach { addFunctions(it.specs(useNullFieldInitialization)) }
6364
dependencyProviderMethods.forEach { addFunction(it.spec()) }
6465
dependencies?.let { addType(it.spec()) }
6566
objectsImpl?.let { addType(it.spec()) }
@@ -96,12 +97,20 @@ object KotlinCodeGenerator {
9697
.initializer(name)
9798
.build()
9899

99-
private fun CacheField.spec(): PropertySpec =
100-
PropertySpec.builder(name, Any::class, KModifier.PRIVATE)
101-
.mutable(true)
102-
.addAnnotation(Volatile::class)
103-
.initializer("%T.NONE", None::class)
104-
.build()
100+
private fun CacheField.spec(useNullFieldInitialization: Boolean): PropertySpec =
101+
if (useNullFieldInitialization) {
102+
PropertySpec.builder(name, Any::class.asTypeName().copy(true), KModifier.PRIVATE)
103+
.mutable(true)
104+
.addAnnotation(Volatile::class)
105+
.initializer("null")
106+
.build()
107+
} else {
108+
PropertySpec.builder(name, Any::class, KModifier.PRIVATE)
109+
.mutable(true)
110+
.addAnnotation(Volatile::class)
111+
.initializer("%T.NONE", None::class)
112+
.build()
113+
}
105114

106115
private fun Constructor.spec(): FunSpec =
107116
FunSpec.constructorBuilder()
@@ -194,34 +203,55 @@ object KotlinCodeGenerator {
194203
.addStatement("return this")
195204
.build()
196205

197-
private fun FactoryProviderMethod.specs(): List<FunSpec> {
206+
private fun FactoryProviderMethod.specs(useNullFieldInitialization: Boolean): List<FunSpec> {
198207
val primarySpec =
199208
FunSpec.builder(name)
200209
.addModifiers(KModifier.INTERNAL)
201210
.returns(returnTypeName.reloadedForTypeArgs(env))
202-
.addCode(body.spec())
211+
.addCode(body.spec(useNullFieldInitialization))
203212
.build()
204213
val spreadSpecs = spreadProviderMethods.map { it.spec() }
205214
return listOf(primarySpec) + spreadSpecs
206215
}
207216

208-
private fun FactoryProviderMethodBody.spec(): CodeBlock =
217+
private fun FactoryProviderMethodBody.spec(useNullFieldInitialization: Boolean): CodeBlock =
209218
when (this) {
210-
is FactoryProviderMethodBody.Cached -> spec()
219+
is FactoryProviderMethodBody.Cached -> spec(useNullFieldInitialization)
211220
is FactoryProviderMethodBody.Uncached -> spec()
212221
}
213222

214-
private fun FactoryProviderMethodBody.Cached.spec(): CodeBlock =
215-
CodeBlock.builder()
216-
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
217-
.beginControlFlow("synchronized (this)")
218-
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
219-
.addStatement("%N=%L", cacheFieldName, instantiation.spec())
220-
.endControlFlow()
221-
.endControlFlow()
222-
.endControlFlow()
223-
.add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env))
223+
private fun FactoryProviderMethodBody.Cached.spec(
224+
useNullFieldInitialization: Boolean,
225+
): CodeBlock {
226+
if (useNullFieldInitialization) {
227+
val localFieldName = "_$cacheFieldName"
228+
val codeBlockBuilder =
229+
CodeBlock.builder()
230+
// Using a local variable reduces atomic read overhead
231+
.addStatement("var $localFieldName = %N;\n", cacheFieldName)
232+
.beginControlFlow("if (%N == null)", localFieldName)
233+
.beginControlFlow("synchronized (this)")
234+
.beginControlFlow("if (%N == null)", cacheFieldName)
235+
.addStatement("%N = %L", localFieldName, instantiation.spec())
236+
.addStatement("%N = %N", cacheFieldName, localFieldName)
237+
.endControlFlow()
238+
.endControlFlow()
239+
.endControlFlow()
240+
return codeBlockBuilder
241+
.add("return ( %N as %T )", localFieldName, returnTypeName.reloadedForTypeArgs(env))
224242
.build()
243+
}
244+
return CodeBlock.builder()
245+
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
246+
.beginControlFlow("synchronized (this)")
247+
.beginControlFlow("if (%N == %T.NONE)", cacheFieldName, None::class)
248+
.addStatement("%N=%L", cacheFieldName, instantiation.spec())
249+
.endControlFlow()
250+
.endControlFlow()
251+
.endControlFlow()
252+
.add("return ( %N as %T )", cacheFieldName, returnTypeName.reloadedForTypeArgs(env))
253+
.build()
254+
}
225255

226256
private fun motif.compiler.TypeName.reloadedForTypeArgs(env: XProcessingEnv): TypeName =
227257
if (kt is ParameterizedTypeName) {

compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import motif.ast.compiler.CompilerMethod
3535
* implementations.
3636
*/
3737
class ScopeImpl(
38+
val useNullFieldInitialization: Boolean,
3839
val className: ClassName,
3940
val superClassName: ClassName,
4041
val internalScope: Boolean,

compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ private constructor(
6868
fun create(): ScopeImpl {
6969
val isInternal = (scope.clazz as? CompilerClass)?.isInternal() ?: false
7070
return ScopeImpl(
71+
(scope.clazz.annotations
72+
.find { it.className == motif.Scope::class.java.name }!!
73+
.annotationValueMap[SCOPE_ANNOTATION_FIELD_USE_NULL]
74+
as? Boolean) ?: false,
7175
scope.implClassName,
7276
scope.typeName,
7377
isInternal,
@@ -429,6 +433,7 @@ private constructor(
429433

430434
private const val OBJECTS_FIELD_NAME = "objects"
431435
private const val DEPENDENCIES_FIELD_NAME = "dependencies"
436+
private const val SCOPE_ANNOTATION_FIELD_USE_NULL = "useNullFieldInitialization"
432437

433438
fun create(env: XProcessingEnv, graph: ResolvedGraph): List<ScopeImpl> =
434439
ScopeImplFactory(env, graph).create()

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
#
1616
GROUP=com.uber.motif
17-
VERSION_NAME=0.4.0-alpha07-SNAPSHOT
17+
VERSION_NAME=0.4.0-alpha08-SNAPSHOT
1818
POM_DESCRIPTION=Simple DI API for Android / Java.
1919
POM_URL=https://github.com/uber/motif/
2020
POM_SCM_URL=https://github.com/uber/motif/
@@ -33,4 +33,4 @@ android.useAndroidX=true
3333
android.enableJetifier=true
3434

3535
# https://github.com/Kotlin/dokka/issues/1405
36-
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g
36+
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g

intellij/ast/src/main/kotlin/motif/ast/intellij/IntelliJAnnotation.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.intellij.psi.PsiElementFactory
2525
import com.intellij.psi.PsiField
2626
import com.intellij.psi.PsiReferenceExpression
2727
import com.intellij.psi.PsiSubstitutor
28+
import java.util.Collections
2829
import kotlin.jvm.java
2930
import kotlin.jvm.javaClass
3031
import kotlin.reflect.KClass
@@ -62,6 +63,9 @@ class IntelliJAnnotation(private val project: Project, private val psiAnnotation
6263
override fun matchesClass(annotationClass: KClass<out Annotation>): Boolean =
6364
psiAnnotation.qualifiedName == annotationClass.java.name
6465

66+
override val annotationValueMap: Map<String, Any?>
67+
get() = Collections.emptyMap()
68+
6569
override fun equals(other: Any?): Boolean {
6670
if (this === other) return true
6771
if (javaClass != other?.javaClass) return false

lib/src/main/java/motif/Scope.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@
1515
*/
1616
package motif;
1717

18-
public @interface Scope {}
18+
public @interface Scope {
19+
/**
20+
* @return on false, the field will be initialized with [None.NONE]. Otherwise, null &
21+
* [Initialized.INITIALIZED] will be used to skip the field initialization.
22+
*/
23+
boolean useNullFieldInitialization() default false;
24+
}

models/src/main/kotlin/motif/models/Scope.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import motif.ast.IrClass
1919
import motif.ast.IrType
2020

2121
/** [Wiki](https://github.com/uber/motif/wiki#scope) */
22-
sealed class Scope(val clazz: IrClass) {
23-
22+
sealed class Scope(val useNullFieldInitialization: Boolean, val clazz: IrClass) {
2423
val source by lazy { ScopeSource(this) }
2524
val simpleName: String by lazy { clazz.simpleName }
2625
val qualifiedName: String by lazy { clazz.qualifiedName }
@@ -38,15 +37,16 @@ sealed class Scope(val clazz: IrClass) {
3837
}
3938

4039
class ErrorScope internal constructor(clazz: IrClass, val parsingError: ParsingError) :
41-
Scope(clazz) {
40+
Scope(useNullFieldInitialization = false, clazz) {
4241
override val objects: Objects? = null
4342
override val accessMethods: List<AccessMethod> = emptyList()
4443
override val childMethods: List<ChildMethod> = emptyList()
4544
override val factoryMethods: List<FactoryMethod> = emptyList()
4645
override val dependencies: Dependencies? = null
4746
}
4847

49-
class ValidScope internal constructor(clazz: IrClass) : Scope(clazz) {
48+
class ValidScope internal constructor(clazz: IrClass, useNullFieldInitialization: Boolean = false) :
49+
Scope(useNullFieldInitialization, clazz) {
5050

5151
init {
5252
if (clazz.kind != IrClass.Kind.INTERFACE) throw ScopeMustBeAnInterface(clazz)

0 commit comments

Comments
 (0)