mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 15:39:22 +08:00
Kotlin 2.1 and friends (#1966)
* Kotlin 2.1 and friends * fix kotlin 2.1 aliases nullability (#1982) --------- Co-authored-by: Florian LE FICHER <florian.leficher@gmail.com>
This commit is contained in:
@@ -2,11 +2,10 @@
|
||||
autoService = "1.1.1"
|
||||
jdk = "21"
|
||||
jvmTarget = "1.8"
|
||||
kotlin = "2.0.0"
|
||||
# No 0.5.0 full release yet due to KSP's 1.0.21 release being busted for CLI/programmatic use
|
||||
kotlinCompileTesting = "0.5.0-alpha07"
|
||||
kotlin = "2.1.21"
|
||||
kotlinCompileTesting = "0.7.1"
|
||||
kotlinpoet = "2.2.0"
|
||||
ksp = "2.0.21-1.0.25"
|
||||
ksp = "2.1.21-2.0.1"
|
||||
|
||||
[plugins]
|
||||
dokka = { id = "org.jetbrains.dokka", version = "2.0.0" }
|
||||
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.ksp
|
||||
|
||||
import com.google.devtools.ksp.getClassDeclarationByName
|
||||
import com.google.devtools.ksp.processing.KSPLogger
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
|
@@ -30,8 +30,10 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSType
|
||||
import com.google.devtools.ksp.symbol.KSTypeAlias
|
||||
import com.google.devtools.ksp.symbol.KSTypeParameter
|
||||
import com.google.devtools.ksp.symbol.Modifier
|
||||
import com.google.devtools.ksp.symbol.Nullability
|
||||
import com.google.devtools.ksp.symbol.Origin
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
@@ -258,7 +260,8 @@ private fun KSPropertyDeclaration.toPropertySpec(
|
||||
): PropertySpec {
|
||||
return PropertySpec.builder(
|
||||
name = simpleName.getShortName(),
|
||||
type = resolvedType.toTypeName(typeParameterResolver).unwrapTypeAlias(),
|
||||
type = resolvedType.toTypeName(typeParameterResolver).unwrapTypeAlias()
|
||||
.fixTypeAliasNullability(resolvedType),
|
||||
)
|
||||
.mutable(isMutable)
|
||||
.addModifiers(modifiers.map { KModifier.valueOf(it.name) })
|
||||
@@ -278,3 +281,13 @@ private fun KSPropertyDeclaration.toPropertySpec(
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun TypeName.fixTypeAliasNullability(resolvedType: KSType): TypeName {
|
||||
return if (resolvedType.declaration is KSTypeAlias) {
|
||||
copy(
|
||||
nullable = resolvedType.nullability == Nullability.NULLABLE,
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen.ksp
|
||||
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSAnnotation
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSType
|
||||
import com.google.devtools.ksp.symbol.KSValueArgument
|
||||
import java.lang.reflect.InvocationHandler
|
||||
@@ -32,15 +32,6 @@ import kotlin.reflect.KClass
|
||||
* Copied experimental utilities from KSP.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find a class in the compilation classpath for the given name.
|
||||
*
|
||||
* @param name fully qualified name of the class to be loaded; using '.' as separator.
|
||||
* @return a KSClassDeclaration, or null if not found.
|
||||
*/
|
||||
internal fun Resolver.getClassDeclarationByName(name: String): KSClassDeclaration? =
|
||||
getClassDeclarationByName(getKSNameFromString(name))
|
||||
|
||||
internal fun <T : Annotation> KSAnnotated.getAnnotationsByType(annotationKClass: KClass<T>): Sequence<T> {
|
||||
return this.annotations.filter {
|
||||
it.shortName.getShortName() == annotationKClass.simpleName &&
|
||||
@@ -74,46 +65,67 @@ private fun KSAnnotation.createInvocationHandler(clazz: Class<*>): InvocationHan
|
||||
"$methodName=$value"
|
||||
}.toList()
|
||||
} else {
|
||||
val argument = try {
|
||||
arguments.first { it.name?.asString() == method.name }
|
||||
} catch (e: NullPointerException) {
|
||||
throw IllegalArgumentException("This is a bug using the default KClass for an annotation", e)
|
||||
}
|
||||
val argument = arguments.first { it.name?.asString() == method.name }
|
||||
when (val result = argument.value ?: method.defaultValue) {
|
||||
is Proxy -> result
|
||||
|
||||
is List<*> -> {
|
||||
val value = { result.asArray(method) }
|
||||
val value = { result.asArray(method, clazz) }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
|
||||
else -> {
|
||||
when {
|
||||
// Workaround for java annotation value array type
|
||||
// https://github.com/google/ksp/issues/1329
|
||||
method.returnType.isArray -> {
|
||||
if (result !is Array<*>) {
|
||||
val value = { result.asArray(method, clazz) }
|
||||
cache.getOrPut(Pair(method.returnType, value), value)
|
||||
} else {
|
||||
throw IllegalStateException("unhandled value type, please file a bug at https://github.com/google/ksp/issues/new")
|
||||
}
|
||||
}
|
||||
method.returnType.isEnum -> {
|
||||
val value = { result.asEnum(method.returnType) }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
|
||||
method.returnType.isAnnotation -> {
|
||||
val value = { (result as KSAnnotation).asAnnotation(method.returnType) }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
|
||||
method.returnType.name == "java.lang.Class" -> {
|
||||
val value = { (result as KSType).asClass() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
cache.getOrPut(Pair(method.returnType, result)) {
|
||||
when (result) {
|
||||
is KSType -> result.asClass(clazz)
|
||||
// Handles com.intellij.psi.impl.source.PsiImmediateClassType using reflection
|
||||
// since api doesn't contain a reference to this
|
||||
else -> Class.forName(
|
||||
result.javaClass.methods
|
||||
.first { it.name == "getCanonicalText" }
|
||||
.invoke(result, false) as String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
method.returnType.name == "byte" -> {
|
||||
val value = { result.asByte() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
|
||||
method.returnType.name == "short" -> {
|
||||
val value = { result.asShort() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
|
||||
method.returnType.name == "long" -> {
|
||||
val value = { result.asLong() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
method.returnType.name == "float" -> {
|
||||
val value = { result.asFloat() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
method.returnType.name == "double" -> {
|
||||
val value = { result.asDouble() }
|
||||
cache.getOrPut(Pair(method.returnType, result), value)
|
||||
}
|
||||
else -> result // original value
|
||||
}
|
||||
}
|
||||
@@ -134,42 +146,28 @@ private fun KSAnnotation.asAnnotation(
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun List<*>.asArray(method: Method) =
|
||||
private fun List<*>.asArray(method: Method, proxyClass: Class<*>) =
|
||||
when (method.returnType.componentType.name) {
|
||||
"boolean" -> (this as List<Boolean>).toBooleanArray()
|
||||
|
||||
"byte" -> (this as List<Byte>).toByteArray()
|
||||
|
||||
"short" -> (this as List<Short>).toShortArray()
|
||||
|
||||
"char" -> (this as List<Char>).toCharArray()
|
||||
|
||||
"double" -> (this as List<Double>).toDoubleArray()
|
||||
|
||||
"float" -> (this as List<Float>).toFloatArray()
|
||||
|
||||
"int" -> (this as List<Int>).toIntArray()
|
||||
|
||||
"long" -> (this as List<Long>).toLongArray()
|
||||
|
||||
"java.lang.Class" -> (this as List<KSType>).map {
|
||||
Class.forName(it.declaration.qualifiedName!!.asString())
|
||||
}.toTypedArray()
|
||||
|
||||
"java.lang.Class" -> (this as List<KSType>).asClasses(proxyClass).toTypedArray()
|
||||
"java.lang.String" -> (this as List<String>).toTypedArray()
|
||||
|
||||
else -> { // arrays of enums or annotations
|
||||
when {
|
||||
method.returnType.componentType.isEnum -> {
|
||||
this.toArray(method) { result -> result.asEnum(method.returnType.componentType) }
|
||||
}
|
||||
|
||||
method.returnType.componentType.isAnnotation -> {
|
||||
this.toArray(method) { result ->
|
||||
(result as KSAnnotation).asAnnotation(method.returnType.componentType)
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unable to process type ${method.returnType.componentType.name}")
|
||||
}
|
||||
}
|
||||
@@ -192,9 +190,10 @@ private fun <T> Any.asEnum(returnType: Class<T>): T =
|
||||
returnType.getDeclaredMethod("valueOf", String::class.java)
|
||||
.invoke(
|
||||
null,
|
||||
// Change from upstream KSP - https://github.com/google/ksp/pull/685
|
||||
if (this is KSType) {
|
||||
this.declaration.simpleName.getShortName()
|
||||
} else if (this is KSClassDeclaration) {
|
||||
this.simpleName.getShortName()
|
||||
} else {
|
||||
this.toString()
|
||||
},
|
||||
@@ -204,4 +203,53 @@ private fun Any.asByte(): Byte = if (this is Int) this.toByte() else this as Byt
|
||||
|
||||
private fun Any.asShort(): Short = if (this is Int) this.toShort() else this as Short
|
||||
|
||||
private fun KSType.asClass() = Class.forName(this.declaration.qualifiedName!!.asString())
|
||||
private fun Any.asLong(): Long = if (this is Int) this.toLong() else this as Long
|
||||
|
||||
private fun Any.asFloat(): Float = if (this is Int) this.toFloat() else this as Float
|
||||
|
||||
private fun Any.asDouble(): Double = if (this is Int) this.toDouble() else this as Double
|
||||
|
||||
// for Class/KClass member
|
||||
internal class KSTypeNotPresentException(val ksType: KSType, cause: Throwable) : RuntimeException(cause)
|
||||
|
||||
// for Class[]/Array<KClass<*>> member.
|
||||
internal class KSTypesNotPresentException(val ksTypes: List<KSType>, cause: Throwable) : RuntimeException(cause)
|
||||
|
||||
private fun KSType.asClass(proxyClass: Class<*>) = try {
|
||||
Class.forName(this.declaration.toJavaClassName(), true, proxyClass.classLoader)
|
||||
} catch (e: Exception) {
|
||||
throw KSTypeNotPresentException(this, e)
|
||||
}
|
||||
|
||||
private fun List<KSType>.asClasses(proxyClass: Class<*>) = try {
|
||||
this.map { type -> type.asClass(proxyClass) }
|
||||
} catch (e: Exception) {
|
||||
throw KSTypesNotPresentException(this, e)
|
||||
}
|
||||
|
||||
private fun Any.asArray(method: Method, proxyClass: Class<*>) = listOf(this).asArray(method, proxyClass)
|
||||
|
||||
private fun KSDeclaration.toJavaClassName(): String {
|
||||
val nameDelimiter = '.'
|
||||
val packageNameString = packageName.asString()
|
||||
val qualifiedNameString = qualifiedName!!.asString()
|
||||
val simpleNames = qualifiedNameString
|
||||
.removePrefix("${packageNameString}$nameDelimiter")
|
||||
.split(nameDelimiter)
|
||||
|
||||
return if (simpleNames.size > 1) {
|
||||
buildString {
|
||||
append(packageNameString)
|
||||
append(nameDelimiter)
|
||||
|
||||
simpleNames.forEachIndexed { index, s ->
|
||||
if (index > 0) {
|
||||
append('$')
|
||||
}
|
||||
append(s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qualifiedNameString
|
||||
}
|
||||
}
|
||||
|
@@ -35,8 +35,9 @@ tasks.withType<Test>().configureEach {
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
allWarningsAsErrors.set(true)
|
||||
freeCompilerArgs.add(
|
||||
freeCompilerArgs.addAll(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-Xannotation-default-target=param-property",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -36,8 +36,9 @@ tasks.withType<Test>().configureEach {
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
allWarningsAsErrors.set(true)
|
||||
freeCompilerArgs.add(
|
||||
freeCompilerArgs.addAll(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-Xannotation-default-target=param-property",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -487,7 +487,7 @@ class DualKotlinTest {
|
||||
val parameterized: GenericClass<TypeAlias>,
|
||||
val wildcardIn: GenericClass<in TypeAlias>,
|
||||
val wildcardOut: GenericClass<out TypeAlias>,
|
||||
val complex: GenericClass<GenericTypeAlias>?,
|
||||
val complex: GenericClass<GenericTypeAlias?>?,
|
||||
)
|
||||
|
||||
// Regression test for https://github.com/square/moshi/issues/991
|
||||
|
@@ -3,7 +3,8 @@ import com.vanniktech.maven.publish.KotlinJvm
|
||||
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
@@ -12,50 +13,30 @@ plugins {
|
||||
id("org.jetbrains.dokka")
|
||||
}
|
||||
|
||||
val mainSourceSet by sourceSets.named("main")
|
||||
val java16: SourceSet by sourceSets.creating {
|
||||
java {
|
||||
srcDir("src/main/java16")
|
||||
kotlin.target {
|
||||
val main = compilations.getByName(MAIN_COMPILATION_NAME)
|
||||
val java16 =
|
||||
compilations.create("java16") {
|
||||
associateWith(main)
|
||||
defaultSourceSet.kotlin.srcDir("src/main/java16")
|
||||
compileJavaTaskProvider.configure {
|
||||
options.release = 16
|
||||
}
|
||||
compileTaskProvider.configure {
|
||||
(compilerOptions as KotlinJvmCompilerOptions).jvmTarget = JvmTarget.JVM_16
|
||||
}
|
||||
|
||||
// We use newer JDKs but target 16 for maximum compatibility
|
||||
val service = project.extensions.getByType<JavaToolchainService>()
|
||||
val customLauncher =
|
||||
service.launcherFor {
|
||||
languageVersion.set(libs.versions.jdk.map(JavaLanguageVersion::of))
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>("compileJava16Java") {
|
||||
options.release.set(16)
|
||||
}
|
||||
|
||||
tasks.named<KotlinCompile>("compileJava16Kotlin") {
|
||||
kotlinJavaToolchain.toolchain.use(customLauncher)
|
||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_16)
|
||||
}
|
||||
|
||||
// Grant our java16 sources access to internal APIs in the main source set
|
||||
kotlin.target.compilations.run {
|
||||
getByName("java16")
|
||||
.associateWith(getByName(KotlinCompilation.MAIN_COMPILATION_NAME))
|
||||
}
|
||||
|
||||
// Package our actual RecordJsonAdapter from java16 sources in and denote it as an MRJAR
|
||||
tasks.named<Jar>("jar") {
|
||||
tasks.named<Jar>(artifactsTaskName) {
|
||||
from(java16.output) {
|
||||
into("META-INF/versions/16")
|
||||
exclude("META-INF")
|
||||
}
|
||||
manifest {
|
||||
attributes("Multi-Release" to "true")
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
"java16Implementation" {
|
||||
extendsFrom(api.get())
|
||||
extendsFrom(implementation.get())
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
@@ -78,8 +59,6 @@ tasks
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// So the j16 source set can "see" main Moshi sources
|
||||
"java16Implementation"(mainSourceSet.output)
|
||||
compileOnly(libs.jsr305)
|
||||
api(libs.okio)
|
||||
|
||||
|
1
moshi/gradle.properties
Normal file
1
moshi/gradle.properties
Normal file
@@ -0,0 +1 @@
|
||||
kotlin.build.archivesTaskOutputAsFriendModule=false
|
Reference in New Issue
Block a user