Skip to content

Commit

Permalink
Gracefully handle lambdas registered as extensions
Browse files Browse the repository at this point in the history
and coverage for lost type info with typed Java and Kotlin lambdas

Signed-off-by: Paul Merlin <paul@gradle.com>
  • Loading branch information
eskatos committed Sep 18, 2019
1 parent 01b2aa7 commit aa0d471
Show file tree
Hide file tree
Showing 2 changed files with 307 additions and 3 deletions.
@@ -0,0 +1,292 @@
/*
* Copyright 2019 the original author or authors.
*
* 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 org.gradle.kotlin.dsl.integration

import org.gradle.test.fixtures.file.LeaksFileHandles
import org.gradle.test.fixtures.plugin.PluginBuilder

import org.hamcrest.CoreMatchers.containsString

import org.junit.ComparisonFailure
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException

import spock.lang.Issue


@LeaksFileHandles("Kotlin Compiler Daemon working directory")
class ProjectSchemaLambdaAccessorsIntegrationTest : AbstractPluginIntegrationTest() {

@get:Rule
val exceptionRule: ExpectedException = ExpectedException.none()

@Test
fun `accessors to **untyped** groovy closures extensions are typed Any`() {

withDefaultSettings()
PluginBuilder(file("buildSrc")).apply {
addPlugin(
"""
project.extensions.add("closureExtension", { String name ->
name.toUpperCase()
})
""".trimIndent(),
"my"
)
generateForBuildSrc()
}

withBuildScript("""
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("closureExtension: " + typeOf(closureExtension))
val casted = closureExtension as groovy.lang.Closure<*>
println(casted.call("some"))
""")

build("help").apply {
assertOutputContains("closureExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **untyped** kotlin lambda extensions are typed Any`() {

requireGradleDistributionOnEmbeddedExecuter()

withDefaultSettings()
withKotlinBuildSrc()
withFile("buildSrc/src/main/kotlin/my.gradle.kts", """
extensions.add("lambdaExtension", { name: String ->
name.toUpperCase()
})
""")

withBuildScript("""
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("lambdaExtension: " + typeOf(lambdaExtension))
val casted = lambdaExtension as (String) -> String
println(casted.invoke("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **untyped** java lambda extensions are typed Any`() {

withDefaultSettings()
withFile("buildSrc/build.gradle", """
plugins {
id("java")
id("java-gradle-plugin")
}
gradlePlugin {
plugins {
my {
id = "my"
implementationClass = "my.MyPlugin"
}
}
}
""")
withFile("buildSrc/src/main/java/my/MyPlugin.java", """
package my;
import org.gradle.api.*;
import java.util.function.Function;
public class MyPlugin implements Plugin<Project> {
public void apply(Project project) {
Function<String, String> lambda = s -> s.toUpperCase();
project.getExtensions().add("lambdaExtension", lambda);
}
}
""")

withBuildScript("""
import java.util.function.Function
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("lambdaExtension: " + typeOf(lambdaExtension))
val casted = lambdaExtension as Function<String, String>
println(casted.apply("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **typed** groovy closures extensions are typed`() {

withDefaultSettings()
PluginBuilder(file("buildSrc")).apply {
addPlugin(
"""
def typeToken = new org.gradle.api.reflect.TypeOf<Closure<String>>() {}
project.extensions.add(typeToken, "closureExtension", { String name ->
name.toUpperCase()
})
""".trimIndent(),
"my"
)
generateForBuildSrc()
}

withBuildScript("""
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("closureExtension: " + typeOf(closureExtension))
println(closureExtension.call("some"))
""")

build("help").apply {
assertOutputContains("closureExtension: groovy.lang.Closure<java.lang.String>")
assertOutputContains("SOME")
}
}

@Test
@Issue("https://github.com/gradle/gradle/issues/10772")
fun `accessors to **typed** kotlin lambda extensions are typed`() {

// TODO:kotlin-dsl Remove once above issue is fixed
exceptionRule.apply {
expect(ComparisonFailure::class.java)
expectMessage(containsString("lambdaExtension: kotlin.jvm.functions.Function1<? super java.lang.Object, ? extends java.lang.String>"))
}

requireGradleDistributionOnEmbeddedExecuter()

withDefaultSettings()
withKotlinBuildSrc()
withFile("buildSrc/src/main/kotlin/my.gradle.kts", """
val typeToken = typeOf<(String) -> String>()
val lambda = { name: String -> name.toUpperCase() }
extensions.add(typeToken, "lambdaExtension", lambda)
""")

withBuildScript("""
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("lambdaExtension: " + typeOf(lambdaExtension))
println(lambdaExtension("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: kotlin.jvm.functions.Function1<? super java.lang.String, ? extends java.lang.String>")
assertOutputContains("SOME")
}
}

@Test
@Issue("https://github.com/gradle/gradle/issues/10771")
fun `accessors to **typed** java lambda extensions are typed`() {

// TODO:kotlin-dsl Remove once above issue is fixed
exceptionRule.apply {
expect(ComparisonFailure::class.java)
expectMessage(containsString("lambdaExtension: java.lang.Object"))
}

withDefaultSettings()
withFile("buildSrc/build.gradle", """
plugins {
id("java")
id("java-gradle-plugin")
}
gradlePlugin {
plugins {
my {
id = "my"
implementationClass = "my.MyPlugin"
}
}
}
""")
withFile("buildSrc/src/main/java/my/MyPlugin.java", """
package my;
import org.gradle.api.*;
import org.gradle.api.reflect.*;
import java.util.function.Function;
public class MyPlugin implements Plugin<Project> {
public void apply(Project project) {
TypeOf<Function<String, String>> typeToken = new TypeOf<Function<String, String>>() {};
Function<String, String> lambda = s -> s.toUpperCase();
project.getExtensions().add(typeToken, "lambdaExtension", lambda);
}
}
""")

withBuildScript("""
import java.util.function.Function
plugins {
my
}
inline fun <reified T> typeOf(value: T) = typeOf<T>()
println("lambdaExtension: " + typeOf(lambdaExtension))
val casted = lambdaExtension as Function<String, String>
println(casted.apply("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.util.function.Function<java.lang.String, java.lang.String>")
assertOutputContains("SOME")
}
}
}
Expand Up @@ -38,7 +38,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult

import kotlin.reflect.KClass
import kotlin.reflect.KVisibility
import kotlin.reflect.full.superclasses

import java.lang.reflect.Modifier


class DefaultProjectSchemaProvider : ProjectSchemaProvider {
Expand Down Expand Up @@ -149,8 +150,19 @@ val KClass<*>.firstKotlinPublicOrSelf

private
val KClass<*>.firstKotlinPublicOrNull: KClass<*>?
get() = takeIf { visibility == KVisibility.PUBLIC }
?: superclasses.firstNotNullResult { it.firstKotlinPublicOrNull }
get() = takeIf { isJavaPublic && isKotlinVisible && visibility == KVisibility.PUBLIC }
?: (java.superclass as Class<*>?)?.kotlin?.firstKotlinPublicOrNull
?: java.interfaces.firstNotNullResult { it.kotlin.firstKotlinPublicOrNull }


private
val KClass<*>.isJavaPublic
get() = Modifier.isPublic(java.modifiers)


private
val KClass<*>.isKotlinVisible: Boolean
get() = !java.isLocalClass && !java.isAnonymousClass && !java.isSynthetic


private
Expand Down

0 comments on commit aa0d471

Please sign in to comment.