Skip to content

Commit

Permalink
Add Creatable api. Remove @Dependencies.
Browse files Browse the repository at this point in the history
  • Loading branch information
Leland Takamine committed Dec 3, 2019
1 parent 56b1729 commit 6485ab2
Show file tree
Hide file tree
Showing 129 changed files with 918 additions and 257 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,18 @@ ChildScope childScope = mainScope.child();

## Root Scopes

If Motif finds a nested interface annotated with `@Dependencies` on a Scope, it uses that interface to define exactly what this scope needs from its parent. This is required in order for Motif to tell you when you have unsatisfied dependencies. The recommended pattern is to always declare an empty `@Dependencies` interface on root Scopes:
By extending `Creatable<D>` you can specify exactly the dependencies you expect from the parent `Scope`. This allows
Motif to report missing dependencies at compile time.

```java
@motif.Scope
interface MainScope {
interface MainScope extends Creatable<MainDependencies> {

// ...

@motif.Dependencies
interface Dependencies {}
}
```

With the root `Dependencies` interface in place, Motif will report any missing dependencies at build time. Without it, missing dependencies will still cause the build to fail, but the error messages will be less intuitive.
interface MainDependencies {}
```

## Convenience APIs

Expand Down
2 changes: 1 addition & 1 deletion ast/src/main/kotlin/motif/ast/IrClass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import kotlin.reflect.KClass
interface IrClass : IrAnnotated, IrHasModifiers {

val type: IrType
val superclass: IrType?
val supertypes: List<IrType>
val typeArguments: List<IrType>
val kind: Kind
val methods: List<IrMethod>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@ class CompilerClass(

override val type: IrType by lazy { CompilerType(env, declaredType) }

override val superclass: IrType? by lazy {
val superType = env.typeUtils.directSupertypes(declaredType).firstOrNull {
it.kind == TypeKind.DECLARED && (it as DeclaredType).asElement().kind == ElementKind.CLASS
} ?: return@lazy null
CompilerType(env, superType as DeclaredType)
override val supertypes: List<IrType> by lazy {
env.typeUtils.directSupertypes(declaredType).map { CompilerType(env, it) }
}

override val typeArguments: List<IrType> by lazy { declaredType.typeArguments.map { CompilerType(env, it) } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import org.junit.Test
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind

class CompilerClassTest {

Expand All @@ -38,7 +37,7 @@ class CompilerClassTest {
class Foo extends Bar<String> {}
""".trimIndent())

val superClass = fooClass.superclass!!.resolveClass() as CompilerClass
val superClass = fooClass.supertypes.single().resolveClass() as CompilerClass
assertThat(superClass.typeArguments.map { it.qualifiedName }).containsExactly("java.lang.String")
}

Expand All @@ -54,8 +53,8 @@ class CompilerClassTest {
class Foo extends Bar<String> {}
""".trimIndent())

val barClass = fooClass.superclass!!.resolveClass() as CompilerClass
val bazClass = barClass.superclass!!.resolveClass() as CompilerClass
val barClass = fooClass.supertypes.single().resolveClass() as CompilerClass
val bazClass = barClass.supertypes.single().resolveClass() as CompilerClass
assertThat(bazClass.typeArguments.map { it.qualifiedName }).containsExactly("java.lang.String")
}

Expand All @@ -69,25 +68,25 @@ class CompilerClassTest {
class Foo<T> extends Bar<T> {}
""".trimIndent())

val superClass = fooClass.superclass!!.resolveClass() as CompilerClass
val superClass = fooClass.supertypes.single().resolveClass() as CompilerClass
assertThat(superClass.typeArguments).isEmpty()
}

@Test
fun testObjectSuperclass() {
fun testObjectSupertype() {
val fooClass = createClass("test.Foo", """
package test;
class Foo {}
""".trimIndent())

val objectClass = fooClass.superclass!!.resolveClass()!!
val objectClass = fooClass.supertypes.single().resolveClass()!!
assertThat(objectClass.qualifiedName).isEqualTo("java.lang.Object")
assertThat(objectClass.superclass).isNull()
assertThat(objectClass.supertypes).isEmpty()
}

@Test
fun testSuperclassOnlyInterface() {
fun testSupertypeOnlyInterface() {
val fooClass = createClass("test.Foo", """
package test;
Expand All @@ -96,9 +95,24 @@ class CompilerClassTest {
class Foo implements Bar {}
""".trimIndent())

val objectClass = fooClass.superclass!!.resolveClass()!!
assertThat(objectClass.qualifiedName).isEqualTo("java.lang.Object")
assertThat(objectClass.superclass).isNull()
val superTypes = fooClass.supertypes.map { it.qualifiedName }
assertThat(superTypes).containsExactly("java.lang.Object", "test.Bar")
}

@Test
fun testSupertypeMultipleInterfaces() {
val fooClass = createClass("test.Foo", """
package test;
interface Bar {}
interface Baz {}
class Foo implements Bar, Baz {}
""".trimIndent())

val superTypes = fooClass.supertypes.map { it.qualifiedName }
assertThat(superTypes).containsExactly("java.lang.Object", "test.Bar", "test.Baz")
}

private fun createClass(qualifiedName: String, @Language("JAVA") text: String): CompilerClass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object JavaCodeGenerator {
addMethod(scopeProviderMethod.spec())
factoryProviderMethods.forEach { addMethods(it.specs()) }
dependencyProviderMethods.forEach { addMethod(it.spec()) }
addType(dependencies.spec())
dependencies?.let { addType(it.spec()) }
objectsImpl?.let { addType(it.spec()) }
}.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object KotlinCodeGenerator {
addFunction(scopeProviderMethod.spec())
factoryProviderMethods.forEach { addFunctions(it.specs()) }
dependencyProviderMethods.forEach { addFunction(it.spec()) }
addType(dependencies.spec())
dependencies?.let { addType(it.spec()) }
objectsImpl?.let { addType(it.spec()) }
}.build()
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/main/kotlin/motif/compiler/ScopeImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ScopeImpl(
val factoryProviderMethods: List<FactoryProviderMethod>,
val dependencyProviderMethods: List<DependencyProviderMethod>,
val objectsImpl: ObjectsImpl?,
val dependencies: Dependencies)
val dependencies: Dependencies?)

/**
* ```
Expand Down Expand Up @@ -122,7 +122,7 @@ class AccessMethodImpl(
* ```
* @Override
* public ChildScope childScope([ ChildMethpdImplParameters ]) {
* return [ ChildDependenciesImpl ];
* return new ChildScopeImpl([ ChildDependenciesImpl ]);
* }
* ```
*/
Expand Down
14 changes: 11 additions & 3 deletions compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ class ScopeImplFactory private constructor(
abstractMethods)
}

private fun dependencies(): Dependencies {
private fun dependencies(): Dependencies? {
if (scope.dependencies != null) {
return null
}
val methods = getDependencyMethodData(scope).map { methodData ->
val qualifier = methodData.returnType.qualifier?.let { annotation ->
Qualifier(annotation as CompilerAnnotation)
Expand Down Expand Up @@ -309,12 +312,17 @@ class ScopeImplFactory private constructor(

private fun createDependencyMethods(scope: Scope): List<DependencyMethodData> {
val nameScope = NameScope()
fun getName(type: Type): String {
val dependencies = scope.dependencies ?: return nameScope.name(type)
val method = dependencies.methodByType[type] ?: throw IllegalStateException("Could not find Dependencies method for type: $type")
return method.method.name
}
return graph.getUnsatisfied(scope)
.toSortedMap()
.entries
.map { (type, sinks) ->
DependencyMethodData(
nameScope.name(type),
getName(type),
type.type.typeName,
type,
sinks)
Expand All @@ -339,7 +347,7 @@ class ScopeImplFactory private constructor(

private val Scope.dependenciesClassName: ClassName
get() = dependenciesClassNames.computeIfAbsent(this) {
implClassName.nestedClass("Dependencies")
dependencies?.clazz?.typeName ?: implClassName.nestedClass("Dependencies")
}

private val Scope.objectsClassName: ClassName?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class DependencyMethodWithParametersHandler(private val error: Dependen

override fun StringBuilder.handle() {
appendln("""
Methods on @Dependencies-annotated interfaces must be parameterless:
Methods on dependencies interfaces must be parameterless:
${error.dependenciesClass.qualifiedName}.${error.method.name}
""".trimIndent())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 motif.errormessage

import motif.models.DuplicatedDependenciesMethod

internal class DuplicatedDependenciesMethodHandler(private val error: DuplicatedDependenciesMethod) : ErrorHandler {

override val name = "DUPLICATED DEPENDENCIES METHOD"

override fun StringBuilder.handle() {
appendln("Multiple dependencies methods of the same type:\n")
append(error.duplicatedMethods.joinToString("\n") { " * ${it.qualifiedName}" })
appendln()
appendln()
appendln("""
Suggestions:
* Remove the redundant methods
""".trimIndent())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal interface ErrorHandler {
is NullableSpreadMethod -> NullableSpreadMethodHandler(error)
is InvalidQualifier -> InvalidQualifierHandler(error)
is DuplicatedChildParameterSource -> DuplicatedChildParameterSourceHandler(error)
is DuplicatedDependenciesMethod -> DuplicatedDependenciesMethodHandler(error)
}
is ProcessingError -> when (error) {
is ScopeCycleError -> ScopeCycleHandler(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class VoidDependenciesMethodHandler(private val error: VoidDependencies

override fun StringBuilder.handle() {
appendln("""
Methods on @Dependencies-annotated interfaces must be non-void:
Methods on dependencies interfaces must be non-void:
void ${error.dependenciesClass.qualifiedName}.${error.method.name}
""".trimIndent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ class IntelliJClass(

override val type: IrType by lazy { IntelliJType(project, psiClassType) }

override val superclass: IrType? by lazy {
val superType = psiClassType.superTypes.firstOrNull() ?: return@lazy null
IntelliJType(project, superType)
override val supertypes: List<IrType> by lazy {
psiClassType.superTypes.map { IntelliJType(project, it) }
}

override val typeArguments: List<IrType> by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package motif.ast.intellij

import com.google.common.truth.Truth
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.PsiElementFactory
import com.intellij.testFramework.LightProjectDescriptor
Expand Down Expand Up @@ -189,7 +190,7 @@ class IntelliJClassTest : LightCodeInsightFixtureTestCase() {
class Bar<T> {}
""".trimIndent())

val superClass = fooClass.superclass!!.resolveClass() as IntelliJClass
val superClass = fooClass.supertypes.single().resolveClass() as IntelliJClass
assertThat(superClass.typeArguments.map { it.qualifiedName }).containsExactly("java.lang.String")
}

Expand All @@ -205,8 +206,8 @@ class IntelliJClassTest : LightCodeInsightFixtureTestCase() {
class Baz<T> {}
""".trimIndent())

val barClass = fooClass.superclass!!.resolveClass() as IntelliJClass
val bazClass = barClass.superclass!!.resolveClass() as IntelliJClass
val barClass = fooClass.supertypes.single().resolveClass() as IntelliJClass
val bazClass = barClass.supertypes.single().resolveClass() as IntelliJClass
assertThat(bazClass.typeArguments.map { it.qualifiedName }).containsExactly("java.lang.String")
}

Expand All @@ -219,7 +220,7 @@ class IntelliJClassTest : LightCodeInsightFixtureTestCase() {
class Bar<T> {}
""".trimIndent())

val superClass = fooClass.superclass!!.resolveClass() as IntelliJClass
val superClass = fooClass.supertypes.single().resolveClass() as IntelliJClass
assertThat(superClass.typeArguments).isEmpty()
}

Expand All @@ -230,9 +231,9 @@ class IntelliJClassTest : LightCodeInsightFixtureTestCase() {
class Foo {}
""".trimIndent())

val objectClass = fooClass.superclass!!.resolveClass()!!
val objectClass = fooClass.supertypes.single().resolveClass()!!
assertThat(objectClass.qualifiedName).isEqualTo("java.lang.Object")
assertThat(objectClass.superclass).isNull()
assertThat(objectClass.supertypes).isEmpty()
}

fun testSuperclassOnlyInterface() {
Expand All @@ -244,9 +245,24 @@ class IntelliJClassTest : LightCodeInsightFixtureTestCase() {
interface Bar {}
""".trimIndent())

val objectClass = fooClass.superclass!!.resolveClass()!!
assertThat(objectClass.qualifiedName).isEqualTo("java.lang.Object")
assertThat(objectClass.superclass).isNull()
val superTypes = fooClass.supertypes.map { it.qualifiedName }
assertThat(superTypes).containsExactly("java.lang.Object", "test.Bar")
}

@Test
fun testSupertypeMultipleInterfaces() {
val fooClass = createIntelliJClass("""
package test;
class Foo implements Bar, Baz {}
interface Bar {}
interface Baz {}
""".trimIndent())

val superTypes = fooClass.supertypes.map { it.qualifiedName }
assertThat(superTypes).containsExactly("java.lang.Object", "test.Bar", "test.Baz")
}

private fun createIntelliJClass(@Language("JAVA") classText: String): IntelliJClass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
*/
package motif;

public @interface Dependencies {}
public interface Creatable<D> {}
3 changes: 3 additions & 0 deletions lib/src/main/java/motif/NoDependencies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package motif;

public interface NoDependencies {}
Loading

0 comments on commit 6485ab2

Please sign in to comment.