diff options
author | Zac Sweers <zac.sweers@gmail.com> | 2021-05-06 11:04:22 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-06 11:04:22 -0400 |
commit | 2c072cfb2d06894a75ef5191315c3c19b561c011 (patch) | |
tree | 58e4dbd9c3bbdd962ba9108b209ee030c8620dcf /kotlinpoet | |
parent | ed7a2cb3c8d8f7aa440457be4e469dab0dfb45df (diff) | |
download | kotlinpoet-2c072cfb2d06894a75ef5191315c3c19b561c011.tar.gz |
Update Kotlin + kotlinx-metadata to 1.5 and 0.3.0 (#1079)
Diffstat (limited to 'kotlinpoet')
10 files changed, 837 insertions, 748 deletions
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt index ae64bbbc..8a5ce510 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt @@ -26,6 +26,189 @@ import javax.lang.model.element.TypeElement import kotlin.DeprecationLevel.WARNING import kotlin.reflect.KClass +/** A fully-qualified class name for top-level and member classes. */ +public class ClassName internal constructor( + names: List<String>, + nullable: Boolean = false, + annotations: List<AnnotationSpec> = emptyList(), + tags: Map<KClass<*>, Any> = emptyMap() +) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> { + /** + * Returns a class name created from the given parts. For example, calling this with package name + * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. + */ + @Deprecated("", level = DeprecationLevel.HIDDEN) + public constructor(packageName: String, simpleName: String, vararg simpleNames: String) : + this(listOf(packageName, simpleName, *simpleNames)) + + /** + * Returns a class name created from the given parts. For example, calling this with package name + * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. + */ + public constructor(packageName: String, vararg simpleNames: String) : + this(listOf(packageName, *simpleNames)) { + require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" } + require(simpleNames.none { it.isEmpty() }) { + "simpleNames must not contain empty items: ${simpleNames.contentToString()}" + } + } + + /** + * Returns a class name created from the given parts. For example, calling this with package name + * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. + */ + public constructor(packageName: String, simpleNames: List<String>) : + this(mutableListOf(packageName).apply { addAll(simpleNames) }) { + require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" } + require(simpleNames.none { it.isEmpty() }) { + "simpleNames must not contain empty items: $simpleNames" + } + } + + /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */ + private val names = names.toImmutableList() + + /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */ + public val canonicalName: String = if (names[0].isEmpty()) + names.subList(1, names.size).joinToString(".") else + names.joinToString(".") + + /** Package name, like `"kotlin.collections"` for `Map.Entry`. */ + public val packageName: String get() = names[0] + + /** Simple name of this class, like `"Entry"` for `Map.Entry`. */ + public val simpleName: String get() = names[names.size - 1] + + /** + * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]` + * for `Map.Entry`. + */ + public val simpleNames: List<String> get() = names.subList(1, names.size) + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): ClassName { + return ClassName(names, nullable, annotations, tags) + } + + /** + * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not + * nested in another class. + */ + public fun enclosingClassName(): ClassName? { + return if (names.size != 2) + ClassName(names.subList(0, names.size - 1)) else + null + } + + /** + * Returns the top class in this nesting group. Equivalent to chained calls to + * [ClassName.enclosingClassName] until the result's enclosing class is null. + */ + public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2)) + + /** + * Fully qualified name using `.` to separate package from the top level class name, and `$` to + * separate nested classes, like `kotlin.collections.Map$Entry`. + */ + public fun reflectionName(): String { + // trivial case: no nested names + if (names.size == 2) { + return if (packageName.isEmpty()) + names[1] else + packageName + "." + names[1] + } + // concat top level class name and nested names + return buildString { + append(topLevelClassName().canonicalName) + for (name in simpleNames.subList(1, simpleNames.size)) { + append('$').append(name) + } + } + } + + /** + * Callable reference to the constructor of this class. Emits the enclosing class if one exists, + * followed by the reference operator `::`, followed by either [simpleName] or the + * fully-qualified name if this is a top-level class. + * + * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required + * for a top-level class with a conflicting name. + */ + public fun constructorReference(): CodeBlock { + val enclosing = enclosingClassName() + return if (enclosing != null) { + CodeBlock.of("%T::%N", enclosing, simpleName) + } else { + CodeBlock.of("::%T", this) + } + } + + /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */ + public fun nestedClass(name: String): ClassName = ClassName(names + name) + + /** + * Returns a class that shares the same enclosing package or class. If this class is enclosed by + * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise + * it is equivalent to `get(packageName(), name)`. + */ + public fun peerClass(name: String): ClassName { + val result = names.toMutableList() + result[result.size - 1] = name + return ClassName(result) + } + + /** + * Orders by the fully-qualified name. Nested types are ordered immediately after their + * enclosing type. For example, the following types are ordered by this method: + * + * ``` + * com.example.Robot + * com.example.Robot.Motor + * com.example.RoboticVacuum + * ``` + */ + override fun compareTo(other: ClassName): Int = canonicalName.compareTo(other.canonicalName) + + override fun emit(out: CodeWriter) = + out.emit(out.lookupName(this).escapeSegmentsIfNecessary()) + + public companion object { + /** + * Returns a new [ClassName] instance for the given fully-qualified class name string. This + * method assumes that the input is ASCII and follows typical Java style (lowercase package + * names, UpperCamelCase class names) and may produce incorrect results or throw + * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as + * it can create [ClassName] instances without such restrictions. + */ + @JvmStatic public fun bestGuess(classNameString: String): ClassName { + val names = mutableListOf<String>() + + // Add the package name, like "java.util.concurrent", or "" for no package. + var p = 0 + while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) { + p = classNameString.indexOf('.', p) + 1 + require(p != 0) { "couldn't make a guess for $classNameString" } + } + names += if (p != 0) classNameString.substring(0, p - 1) else "" + + // Add the class names, like "Map" and "Entry". + for (part in classNameString.substring(p).split('.')) { + require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) { + "couldn't make a guess for $classNameString" + } + + names += part + } + + require(names.size >= 2) { "couldn't make a guess for $classNameString" } + return ClassName(names) + } + } +} + @JvmName("get") public fun Class<*>.asClassName(): ClassName { require(!isPrimitive) { "primitive types cannot be represented as a ClassName" } diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt new file mode 100644 index 00000000..1509e918 --- /dev/null +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt @@ -0,0 +1,16 @@ +package com.squareup.kotlinpoet + +import kotlin.reflect.KClass + +public object Dynamic : TypeName(false, emptyList(), TagMap(emptyMap())) { + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): Nothing = throw UnsupportedOperationException("dynamic doesn't support copying") + + override fun emit(out: CodeWriter) = out.apply { + emit("dynamic") + } +} diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt new file mode 100644 index 00000000..b8c4a3e1 --- /dev/null +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt @@ -0,0 +1,95 @@ +package com.squareup.kotlinpoet + +import kotlin.reflect.KClass + +public class LambdaTypeName private constructor( + public val receiver: TypeName? = null, + parameters: List<ParameterSpec> = emptyList(), + public val returnType: TypeName = UNIT, + nullable: Boolean = false, + public val isSuspending: Boolean = false, + annotations: List<AnnotationSpec> = emptyList(), + tags: Map<KClass<*>, Any> = emptyMap() +) : TypeName(nullable, annotations, TagMap(tags)) { + public val parameters: List<ParameterSpec> = parameters.toImmutableList() + + init { + for (param in parameters) { + require(param.annotations.isEmpty()) { "Parameters with annotations are not allowed" } + require(param.modifiers.isEmpty()) { "Parameters with modifiers are not allowed" } + require(param.defaultValue == null) { "Parameters with default values are not allowed" } + } + } + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): LambdaTypeName { + return copy(nullable, annotations, this.isSuspending, tags) + } + + public fun copy( + nullable: Boolean = this.isNullable, + annotations: List<AnnotationSpec> = this.annotations.toList(), + suspending: Boolean = this.isSuspending, + tags: Map<KClass<*>, Any> = this.tags.toMap() + ): LambdaTypeName { + return LambdaTypeName(receiver, parameters, returnType, nullable, suspending, annotations, tags) + } + + override fun emit(out: CodeWriter): CodeWriter { + if (isNullable) { + out.emit("(") + } + + if (isSuspending) { + out.emit("suspend ") + } + + receiver?.let { + if (it.isAnnotated) { + out.emitCode("(%T).", it) + } else { + out.emitCode("%T.", it) + } + } + + parameters.emit(out) + out.emitCode(if (returnType is LambdaTypeName) " -> (%T)" else " -> %T", returnType) + + if (isNullable) { + out.emit(")") + } + return out + } + + public companion object { + /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ + @JvmStatic public fun get( + receiver: TypeName? = null, + parameters: List<ParameterSpec> = emptyList(), + returnType: TypeName + ): LambdaTypeName = LambdaTypeName(receiver, parameters, returnType) + + /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ + @JvmStatic public fun get( + receiver: TypeName? = null, + vararg parameters: TypeName = emptyArray(), + returnType: TypeName + ): LambdaTypeName { + return LambdaTypeName( + receiver, + parameters.toList().map { ParameterSpec.unnamed(it) }, + returnType + ) + } + + /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ + @JvmStatic public fun get( + receiver: TypeName? = null, + vararg parameters: ParameterSpec = emptyArray(), + returnType: TypeName + ): LambdaTypeName = LambdaTypeName(receiver, parameters.toList(), returnType) + } +} diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt index 1b9d69fd..757241a1 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt @@ -17,10 +17,181 @@ package com.squareup.kotlinpoet +import java.lang.reflect.Modifier import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.KTypeParameter +import kotlin.reflect.KTypeProjection +import kotlin.reflect.KVariance + +public class ParameterizedTypeName internal constructor( + private val enclosingType: TypeName?, + public val rawType: ClassName, + typeArguments: List<TypeName>, + nullable: Boolean = false, + annotations: List<AnnotationSpec> = emptyList(), + tags: Map<KClass<*>, Any> = emptyMap() +) : TypeName(nullable, annotations, TagMap(tags)) { + public val typeArguments: List<TypeName> = typeArguments.toImmutableList() + + init { + require(typeArguments.isNotEmpty() || enclosingType != null) { + "no type arguments: $rawType" + } + } + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): ParameterizedTypeName { + return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags) + } + + public fun plusParameter(typeArgument: TypeName): ParameterizedTypeName = + ParameterizedTypeName( + enclosingType, rawType, typeArguments + typeArgument, isNullable, + annotations + ) + + public fun plusParameter(typeArgument: KClass<*>): ParameterizedTypeName = + plusParameter(typeArgument.asClassName()) + + public fun plusParameter(typeArgument: Class<*>): ParameterizedTypeName = + plusParameter(typeArgument.asClassName()) + + override fun emit(out: CodeWriter): CodeWriter { + if (enclosingType != null) { + enclosingType.emitAnnotations(out) + enclosingType.emit(out) + out.emit("." + rawType.simpleName) + } else { + rawType.emitAnnotations(out) + rawType.emit(out) + } + if (typeArguments.isNotEmpty()) { + out.emit("<") + typeArguments.forEachIndexed { index, parameter -> + if (index > 0) out.emit(", ") + parameter.emitAnnotations(out) + parameter.emit(out) + parameter.emitNullable(out) + } + out.emit(">") + } + return out + } + + /** + * Returns a new [ParameterizedTypeName] instance for the specified `name` as nested inside this + * class, with the specified `typeArguments`. + */ + public fun nestedClass(name: String, typeArguments: List<TypeName>): ParameterizedTypeName = + ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments) + + public companion object { + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun ClassName.parameterizedBy( + vararg typeArguments: TypeName + ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments.toList()) + + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun KClass<*>.parameterizedBy( + vararg typeArguments: KClass<*> + ): ParameterizedTypeName = + ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) + + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun Class<*>.parameterizedBy( + vararg typeArguments: Type + ): ParameterizedTypeName = + ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) + + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun ClassName.parameterizedBy( + typeArguments: List<TypeName> + ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments) + + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun KClass<*>.parameterizedBy( + typeArguments: Iterable<KClass<*>> + ): ParameterizedTypeName = + ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) + + /** Returns a parameterized type, applying `typeArguments` to `this`. */ + @JvmStatic @JvmName("get") public fun Class<*>.parameterizedBy( + typeArguments: Iterable<Type> + ): ParameterizedTypeName = + ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) + + /** Returns a parameterized type, applying `typeArgument` to `this`. */ + @JvmStatic @JvmName("get") public fun ClassName.plusParameter( + typeArgument: TypeName + ): ParameterizedTypeName = parameterizedBy(typeArgument) + + /** Returns a parameterized type, applying `typeArgument` to `this`. */ + @JvmStatic @JvmName("get") public fun KClass<*>.plusParameter( + typeArgument: KClass<*> + ): ParameterizedTypeName = parameterizedBy(typeArgument) + + /** Returns a parameterized type, applying `typeArgument` to `this`. */ + @JvmStatic @JvmName("get") public fun Class<*>.plusParameter( + typeArgument: Class<*> + ): ParameterizedTypeName = parameterizedBy(typeArgument) + + /** Returns a parameterized type equivalent to `type`. */ + internal fun get( + type: ParameterizedType, + map: MutableMap<Type, TypeVariableName> + ): ParameterizedTypeName { + val rawType = (type.rawType as Class<*>).asClassName() + val ownerType = if (type.ownerType is ParameterizedType && + !Modifier.isStatic((type.rawType as Class<*>).modifiers) + ) + type.ownerType as ParameterizedType else + null + + val typeArguments = type.actualTypeArguments.map { get(it, map = map) } + return if (ownerType != null) + get(ownerType, map = map).nestedClass(rawType.simpleName, typeArguments) else + ParameterizedTypeName(null, rawType, typeArguments) + } + + /** Returns a type name equivalent to type with given list of type arguments. */ + internal fun get( + type: KClass<*>, + nullable: Boolean, + typeArguments: List<KTypeProjection> + ): TypeName { + if (typeArguments.isEmpty()) { + return type.asTypeName().run { if (nullable) copy(nullable = true) else this } + } + + val effectiveType = if (type.java.isArray) Array<Unit>::class else type + val enclosingClass = type.java.enclosingClass?.kotlin + + return ParameterizedTypeName( + enclosingClass?.let { + get(it, false, typeArguments.drop(effectiveType.typeParameters.size)) + }, + effectiveType.asTypeName(), + typeArguments.take(effectiveType.typeParameters.size).map { (paramVariance, paramType) -> + val typeName = paramType?.asTypeName() ?: return@map STAR + when (paramVariance) { + null -> STAR + KVariance.INVARIANT -> typeName + KVariance.IN -> WildcardTypeName.consumerOf(typeName) + KVariance.OUT -> WildcardTypeName.producerOf(typeName) + } + }, + nullable, + effectiveType.annotations.map { AnnotationSpec.get(it) } + ) + } + } +} /** Returns a parameterized type equivalent to `type`. */ @JvmName("get") diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt index 7bf5969d..26000030 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt @@ -19,12 +19,10 @@ package com.squareup.kotlinpoet import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import java.lang.reflect.GenericArrayType -import java.lang.reflect.Modifier.isStatic import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType -import java.util.Collections import javax.lang.model.element.Modifier import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeParameterElement @@ -38,8 +36,6 @@ import javax.lang.model.type.TypeMirror import javax.lang.model.util.SimpleTypeVisitor7 import kotlin.DeprecationLevel.WARNING import kotlin.reflect.KClass -import kotlin.reflect.KTypeProjection -import kotlin.reflect.KVariance import kotlin.reflect.typeOf /** @@ -298,739 +294,3 @@ public fun Type.asTypeName(): TypeName = TypeName.get(this, mutableMapOf()) @ExperimentalStdlibApi public inline fun <reified T> typeNameOf(): TypeName = typeOf<T>().asTypeName() - -/** A fully-qualified class name for top-level and member classes. */ -public class ClassName internal constructor( - names: List<String>, - nullable: Boolean = false, - annotations: List<AnnotationSpec> = emptyList(), - tags: Map<KClass<*>, Any> = emptyMap() -) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> { - /** - * Returns a class name created from the given parts. For example, calling this with package name - * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. - */ - @Deprecated("", level = DeprecationLevel.HIDDEN) - public constructor(packageName: String, simpleName: String, vararg simpleNames: String) : - this(listOf(packageName, simpleName, *simpleNames)) - - /** - * Returns a class name created from the given parts. For example, calling this with package name - * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. - */ - public constructor(packageName: String, vararg simpleNames: String) : - this(listOf(packageName, *simpleNames)) { - require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" } - require(simpleNames.none { it.isEmpty() }) { - "simpleNames must not contain empty items: ${simpleNames.contentToString()}" - } - } - - /** - * Returns a class name created from the given parts. For example, calling this with package name - * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`. - */ - public constructor(packageName: String, simpleNames: List<String>) : - this(mutableListOf(packageName).apply { addAll(simpleNames) }) { - require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" } - require(simpleNames.none { it.isEmpty() }) { - "simpleNames must not contain empty items: $simpleNames" - } - } - - /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */ - private val names = names.toImmutableList() - - /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */ - public val canonicalName: String = if (names[0].isEmpty()) - names.subList(1, names.size).joinToString(".") else - names.joinToString(".") - - /** Package name, like `"kotlin.collections"` for `Map.Entry`. */ - public val packageName: String get() = names[0] - - /** Simple name of this class, like `"Entry"` for `Map.Entry`. */ - public val simpleName: String get() = names[names.size - 1] - - /** - * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]` - * for `Map.Entry`. - */ - public val simpleNames: List<String> get() = names.subList(1, names.size) - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): ClassName { - return ClassName(names, nullable, annotations, tags) - } - - /** - * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not - * nested in another class. - */ - public fun enclosingClassName(): ClassName? { - return if (names.size != 2) - ClassName(names.subList(0, names.size - 1)) else - null - } - - /** - * Returns the top class in this nesting group. Equivalent to chained calls to - * [ClassName.enclosingClassName] until the result's enclosing class is null. - */ - public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2)) - - /** - * Fully qualified name using `.` to separate package from the top level class name, and `$` to - * separate nested classes, like `kotlin.collections.Map$Entry`. - */ - public fun reflectionName(): String { - // trivial case: no nested names - if (names.size == 2) { - return if (packageName.isEmpty()) - names[1] else - packageName + "." + names[1] - } - // concat top level class name and nested names - return buildString { - append(topLevelClassName().canonicalName) - for (name in simpleNames.subList(1, simpleNames.size)) { - append('$').append(name) - } - } - } - - /** - * Callable reference to the constructor of this class. Emits the enclosing class if one exists, - * followed by the reference operator `::`, followed by either [simpleName] or the - * fully-qualified name if this is a top-level class. - * - * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required - * for a top-level class with a conflicting name. - */ - public fun constructorReference(): CodeBlock { - val enclosing = enclosingClassName() - return if (enclosing != null) { - CodeBlock.of("%T::%N", enclosing, simpleName) - } else { - CodeBlock.of("::%T", this) - } - } - - /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */ - public fun nestedClass(name: String): ClassName = ClassName(names + name) - - /** - * Returns a class that shares the same enclosing package or class. If this class is enclosed by - * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise - * it is equivalent to `get(packageName(), name)`. - */ - public fun peerClass(name: String): ClassName { - val result = names.toMutableList() - result[result.size - 1] = name - return ClassName(result) - } - - /** - * Orders by the fully-qualified name. Nested types are ordered immediately after their - * enclosing type. For example, the following types are ordered by this method: - * - * ``` - * com.example.Robot - * com.example.Robot.Motor - * com.example.RoboticVacuum - * ``` - */ - override fun compareTo(other: ClassName): Int = canonicalName.compareTo(other.canonicalName) - - override fun emit(out: CodeWriter) = - out.emit(out.lookupName(this).escapeSegmentsIfNecessary()) - - public companion object { - /** - * Returns a new [ClassName] instance for the given fully-qualified class name string. This - * method assumes that the input is ASCII and follows typical Java style (lowercase package - * names, UpperCamelCase class names) and may produce incorrect results or throw - * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as - * it can create [ClassName] instances without such restrictions. - */ - @JvmStatic public fun bestGuess(classNameString: String): ClassName { - val names = mutableListOf<String>() - - // Add the package name, like "java.util.concurrent", or "" for no package. - var p = 0 - while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) { - p = classNameString.indexOf('.', p) + 1 - require(p != 0) { "couldn't make a guess for $classNameString" } - } - names += if (p != 0) classNameString.substring(0, p - 1) else "" - - // Add the class names, like "Map" and "Entry". - for (part in classNameString.substring(p).split('.')) { - require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) { - "couldn't make a guess for $classNameString" - } - - names += part - } - - require(names.size >= 2) { "couldn't make a guess for $classNameString" } - return ClassName(names) - } - } -} - -public object Dynamic : TypeName(false, emptyList(), TagMap(emptyMap())) { - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): Nothing = throw UnsupportedOperationException("dynamic doesn't support copying") - - override fun emit(out: CodeWriter) = out.apply { - emit("dynamic") - } -} - -public class LambdaTypeName private constructor( - public val receiver: TypeName? = null, - parameters: List<ParameterSpec> = emptyList(), - public val returnType: TypeName = UNIT, - nullable: Boolean = false, - public val isSuspending: Boolean = false, - annotations: List<AnnotationSpec> = emptyList(), - tags: Map<KClass<*>, Any> = emptyMap() -) : TypeName(nullable, annotations, TagMap(tags)) { - public val parameters: List<ParameterSpec> = parameters.toImmutableList() - - init { - for (param in parameters) { - require(param.annotations.isEmpty()) { "Parameters with annotations are not allowed" } - require(param.modifiers.isEmpty()) { "Parameters with modifiers are not allowed" } - require(param.defaultValue == null) { "Parameters with default values are not allowed" } - } - } - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): LambdaTypeName { - return copy(nullable, annotations, this.isSuspending, tags) - } - - public fun copy( - nullable: Boolean = this.isNullable, - annotations: List<AnnotationSpec> = this.annotations.toList(), - suspending: Boolean = this.isSuspending, - tags: Map<KClass<*>, Any> = this.tags.toMap() - ): LambdaTypeName { - return LambdaTypeName(receiver, parameters, returnType, nullable, suspending, annotations, tags) - } - - override fun emit(out: CodeWriter): CodeWriter { - if (isNullable) { - out.emit("(") - } - - if (isSuspending) { - out.emit("suspend ") - } - - receiver?.let { - if (it.isAnnotated) { - out.emitCode("(%T).", it) - } else { - out.emitCode("%T.", it) - } - } - - parameters.emit(out) - out.emitCode(if (returnType is LambdaTypeName) " -> (%T)" else " -> %T", returnType) - - if (isNullable) { - out.emit(")") - } - return out - } - - public companion object { - /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ - @JvmStatic public fun get( - receiver: TypeName? = null, - parameters: List<ParameterSpec> = emptyList(), - returnType: TypeName - ): LambdaTypeName = LambdaTypeName(receiver, parameters, returnType) - - /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ - @JvmStatic public fun get( - receiver: TypeName? = null, - vararg parameters: TypeName = emptyArray(), - returnType: TypeName - ): LambdaTypeName { - return LambdaTypeName( - receiver, - parameters.toList().map { ParameterSpec.unnamed(it) }, - returnType - ) - } - - /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */ - @JvmStatic public fun get( - receiver: TypeName? = null, - vararg parameters: ParameterSpec = emptyArray(), - returnType: TypeName - ): LambdaTypeName = LambdaTypeName(receiver, parameters.toList(), returnType) - } -} - -public class ParameterizedTypeName internal constructor( - private val enclosingType: TypeName?, - public val rawType: ClassName, - typeArguments: List<TypeName>, - nullable: Boolean = false, - annotations: List<AnnotationSpec> = emptyList(), - tags: Map<KClass<*>, Any> = emptyMap() -) : TypeName(nullable, annotations, TagMap(tags)) { - public val typeArguments: List<TypeName> = typeArguments.toImmutableList() - - init { - require(typeArguments.isNotEmpty() || enclosingType != null) { - "no type arguments: $rawType" - } - } - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): ParameterizedTypeName { - return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags) - } - - public fun plusParameter(typeArgument: TypeName): ParameterizedTypeName = - ParameterizedTypeName( - enclosingType, rawType, typeArguments + typeArgument, isNullable, - annotations - ) - - public fun plusParameter(typeArgument: KClass<*>): ParameterizedTypeName = - plusParameter(typeArgument.asClassName()) - - public fun plusParameter(typeArgument: Class<*>): ParameterizedTypeName = - plusParameter(typeArgument.asClassName()) - - override fun emit(out: CodeWriter): CodeWriter { - if (enclosingType != null) { - enclosingType.emitAnnotations(out) - enclosingType.emit(out) - out.emit("." + rawType.simpleName) - } else { - rawType.emitAnnotations(out) - rawType.emit(out) - } - if (typeArguments.isNotEmpty()) { - out.emit("<") - typeArguments.forEachIndexed { index, parameter -> - if (index > 0) out.emit(", ") - parameter.emitAnnotations(out) - parameter.emit(out) - parameter.emitNullable(out) - } - out.emit(">") - } - return out - } - - /** - * Returns a new [ParameterizedTypeName] instance for the specified `name` as nested inside this - * class, with the specified `typeArguments`. - */ - public fun nestedClass(name: String, typeArguments: List<TypeName>): ParameterizedTypeName = - ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments) - - public companion object { - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun ClassName.parameterizedBy( - vararg typeArguments: TypeName - ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments.toList()) - - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun KClass<*>.parameterizedBy( - vararg typeArguments: KClass<*> - ): ParameterizedTypeName = - ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) - - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun Class<*>.parameterizedBy( - vararg typeArguments: Type - ): ParameterizedTypeName = - ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) - - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun ClassName.parameterizedBy( - typeArguments: List<TypeName> - ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments) - - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun KClass<*>.parameterizedBy( - typeArguments: Iterable<KClass<*>> - ): ParameterizedTypeName = - ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) - - /** Returns a parameterized type, applying `typeArguments` to `this`. */ - @JvmStatic @JvmName("get") public fun Class<*>.parameterizedBy( - typeArguments: Iterable<Type> - ): ParameterizedTypeName = - ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() }) - - /** Returns a parameterized type, applying `typeArgument` to `this`. */ - @JvmStatic @JvmName("get") public fun ClassName.plusParameter( - typeArgument: TypeName - ): ParameterizedTypeName = parameterizedBy(typeArgument) - - /** Returns a parameterized type, applying `typeArgument` to `this`. */ - @JvmStatic @JvmName("get") public fun KClass<*>.plusParameter( - typeArgument: KClass<*> - ): ParameterizedTypeName = parameterizedBy(typeArgument) - - /** Returns a parameterized type, applying `typeArgument` to `this`. */ - @JvmStatic @JvmName("get") public fun Class<*>.plusParameter( - typeArgument: Class<*> - ): ParameterizedTypeName = parameterizedBy(typeArgument) - - /** Returns a parameterized type equivalent to `type`. */ - internal fun get( - type: ParameterizedType, - map: MutableMap<Type, TypeVariableName> - ): ParameterizedTypeName { - val rawType = (type.rawType as Class<*>).asClassName() - val ownerType = if (type.ownerType is ParameterizedType && - !isStatic((type.rawType as Class<*>).modifiers) - ) - type.ownerType as ParameterizedType else - null - - val typeArguments = type.actualTypeArguments.map { get(it, map = map) } - return if (ownerType != null) - get(ownerType, map = map).nestedClass(rawType.simpleName, typeArguments) else - ParameterizedTypeName(null, rawType, typeArguments) - } - - /** Returns a type name equivalent to type with given list of type arguments. */ - internal fun get( - type: KClass<*>, - nullable: Boolean, - typeArguments: List<KTypeProjection> - ): TypeName { - if (typeArguments.isEmpty()) { - return type.asTypeName().run { if (nullable) copy(nullable = true) else this } - } - - val effectiveType = if (type.java.isArray) Array<Unit>::class else type - val enclosingClass = type.java.enclosingClass?.kotlin - - return ParameterizedTypeName( - enclosingClass?.let { - get(it, false, typeArguments.drop(effectiveType.typeParameters.size)) - }, - effectiveType.asTypeName(), - typeArguments.take(effectiveType.typeParameters.size).map { (paramVariance, paramType) -> - val typeName = paramType?.asTypeName() ?: return@map STAR - when (paramVariance) { - null -> STAR - KVariance.INVARIANT -> typeName - KVariance.IN -> WildcardTypeName.consumerOf(typeName) - KVariance.OUT -> WildcardTypeName.producerOf(typeName) - } - }, - nullable, - effectiveType.annotations.map { AnnotationSpec.get(it) } - ) - } - } -} - -public class TypeVariableName private constructor( - public val name: String, - public val bounds: List<TypeName>, - - /** Either [KModifier.IN], [KModifier.OUT], or null. */ - public val variance: KModifier? = null, - public val isReified: Boolean = false, - nullable: Boolean = false, - annotations: List<AnnotationSpec> = emptyList(), - tags: Map<KClass<*>, Any> = emptyMap() -) : TypeName(nullable, annotations, TagMap(tags)) { - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): TypeVariableName { - return copy(nullable, annotations, this.bounds, this.isReified, tags) - } - - public fun copy( - nullable: Boolean = this.isNullable, - annotations: List<AnnotationSpec> = this.annotations.toList(), - bounds: List<TypeName> = this.bounds.toList(), - reified: Boolean = this.isReified, - tags: Map<KClass<*>, Any> = this.tagMap.tags - ): TypeVariableName { - return TypeVariableName( - name, bounds.withoutImplicitBound(), variance, reified, nullable, - annotations, tags - ) - } - - private fun List<TypeName>.withoutImplicitBound(): List<TypeName> { - return if (size == 1) this else filterNot { it == NULLABLE_ANY } - } - - override fun emit(out: CodeWriter) = out.emit(name) - - public companion object { - internal fun of( - name: String, - bounds: List<TypeName>, - variance: KModifier? - ): TypeVariableName { - require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) { - "$variance is an invalid variance modifier, the only allowed values are in and out!" - } - require(bounds.isNotEmpty()) { - "$name has no bounds" - } - // Strip Any? from bounds if it is present. - return TypeVariableName(name, bounds, variance) - } - - /** Returns type variable named `name` with `variance` and without bounds. */ - @JvmStatic @JvmName("get") @JvmOverloads - public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName = - of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("get") @JvmOverloads - public operator fun invoke( - name: String, - vararg bounds: TypeName, - variance: KModifier? = null - ): TypeVariableName = - of( - name = name, - bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST), - variance = variance - ) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("get") @JvmOverloads - public operator fun invoke( - name: String, - vararg bounds: KClass<*>, - variance: KModifier? = null - ): TypeVariableName = - of( - name = name, - bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST), - variance = variance - ) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("get") @JvmOverloads - public operator fun invoke( - name: String, - vararg bounds: Type, - variance: KModifier? = null - ): TypeVariableName = - of( - name = name, - bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST), - variance = variance - ) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("get") @JvmOverloads - public operator fun invoke( - name: String, - bounds: List<TypeName>, - variance: KModifier? = null - ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("getWithClasses") @JvmOverloads - public operator fun invoke( - name: String, - bounds: Iterable<KClass<*>>, - variance: KModifier? = null - ): TypeVariableName = - of( - name, - bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST), - variance - ) - - /** Returns type variable named `name` with `variance` and `bounds`. */ - @JvmStatic @JvmName("getWithTypes") @JvmOverloads - public operator fun invoke( - name: String, - bounds: Iterable<Type>, - variance: KModifier? = null - ): TypeVariableName = - of( - name, - bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST), - variance - ) - - /** - * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid - * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a - * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables` - * map before looking up the bounds. Then if we encounter this TypeVariable again while - * constructing the bounds, we can just return it from the map. And, the code that put the entry - * in `variables` will make sure that the bounds are filled in before returning. - */ - internal fun get( - mirror: javax.lang.model.type.TypeVariable, - typeVariables: MutableMap<TypeParameterElement, TypeVariableName> - ): TypeVariableName { - val element = mirror.asElement() as TypeParameterElement - var typeVariableName: TypeVariableName? = typeVariables[element] - if (typeVariableName == null) { - // Since the bounds field is public, we need to make it an unmodifiableList. But we control - // the List that that wraps, which means we can change it before returning. - val bounds = mutableListOf<TypeName>() - val visibleBounds = Collections.unmodifiableList(bounds) - typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds) - typeVariables[element] = typeVariableName - for (typeMirror in element.bounds) { - bounds += get(typeMirror, typeVariables) - } - bounds.remove(ANY) - bounds.remove(JAVA_OBJECT) - if (bounds.isEmpty()) { - bounds.add(NULLABLE_ANY) - } - } - return typeVariableName - } - - /** Returns type variable equivalent to `type`. */ - internal fun get( - type: TypeVariable<*>, - map: MutableMap<Type, TypeVariableName> = mutableMapOf() - ): TypeVariableName { - var result: TypeVariableName? = map[type] - if (result == null) { - val bounds = mutableListOf<TypeName>() - val visibleBounds = Collections.unmodifiableList(bounds) - result = TypeVariableName(type.name, visibleBounds) - map[type] = result - for (bound in type.bounds) { - bounds += get(bound, map) - } - bounds.remove(ANY) - bounds.remove(JAVA_OBJECT) - if (bounds.isEmpty()) { - bounds.add(NULLABLE_ANY) - } - } - return result - } - - internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY) - private val JAVA_OBJECT = ClassName("java.lang", "Object") - } -} - -public class WildcardTypeName private constructor( - outTypes: List<TypeName>, - inTypes: List<TypeName>, - nullable: Boolean = false, - annotations: List<AnnotationSpec> = emptyList(), - tags: Map<KClass<*>, Any> = emptyMap() -) : TypeName(nullable, annotations, TagMap(tags)) { - public val outTypes: List<TypeName> = outTypes.toImmutableList() - public val inTypes: List<TypeName> = inTypes.toImmutableList() - - init { - require(this.outTypes.size == 1) { "unexpected out types: $outTypes" } - } - - override fun copy( - nullable: Boolean, - annotations: List<AnnotationSpec>, - tags: Map<KClass<*>, Any> - ): WildcardTypeName { - return WildcardTypeName(outTypes, inTypes, nullable, annotations, tags) - } - - override fun emit(out: CodeWriter): CodeWriter { - return when { - inTypes.size == 1 -> out.emitCode("in %T", inTypes[0]) - outTypes == STAR.outTypes -> out.emit("*") - else -> out.emitCode("out %T", outTypes[0]) - } - } - - public companion object { - /** - * Returns a type that represents an unknown type that produces `outType`. For example, if - * `outType` is `CharSequence`, this returns `out CharSequence`. If `outType` is `Any?`, this - * returns `*`, which is shorthand for `out Any?`. - */ - @JvmStatic public fun producerOf(outType: TypeName): WildcardTypeName = - WildcardTypeName(listOf(outType), emptyList()) - - @JvmStatic public fun producerOf(outType: Type): WildcardTypeName = - producerOf(outType.asTypeName()) - - @JvmStatic public fun producerOf(outType: KClass<*>): WildcardTypeName = - producerOf(outType.asTypeName()) - - /** - * Returns a type that represents an unknown type that consumes `inType`. For example, if - * `inType` is `String`, this returns `in String`. - */ - @JvmStatic public fun consumerOf(inType: TypeName): WildcardTypeName = - WildcardTypeName(listOf(ANY), listOf(inType)) - - @JvmStatic public fun consumerOf(inType: Type): WildcardTypeName = - consumerOf(inType.asTypeName()) - - @JvmStatic public fun consumerOf(inType: KClass<*>): WildcardTypeName = - consumerOf(inType.asTypeName()) - - internal fun get( - mirror: javax.lang.model.type.WildcardType, - typeVariables: Map<TypeParameterElement, TypeVariableName> - ): TypeName { - val outType = mirror.extendsBound - if (outType == null) { - val inType = mirror.superBound - return if (inType == null) { - STAR - } else { - consumerOf(get(inType, typeVariables)) - } - } else { - return producerOf(get(outType, typeVariables)) - } - } - - internal fun get( - wildcardName: WildcardType, - map: MutableMap<Type, TypeVariableName> - ): TypeName { - return WildcardTypeName( - wildcardName.upperBounds.map { get(it, map = map) }, - wildcardName.lowerBounds.map { get(it, map = map) } - ) - } - } -} diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt index cc8df36c..b3caeff6 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt @@ -17,13 +17,210 @@ package com.squareup.kotlinpoet +import java.lang.reflect.Type +import java.util.Collections import javax.lang.model.element.TypeParameterElement import javax.lang.model.type.TypeMirror import javax.lang.model.type.TypeVariable +import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.KTypeParameter import kotlin.reflect.KVariance +public class TypeVariableName private constructor( + public val name: String, + public val bounds: List<TypeName>, + + /** Either [KModifier.IN], [KModifier.OUT], or null. */ + public val variance: KModifier? = null, + public val isReified: Boolean = false, + nullable: Boolean = false, + annotations: List<AnnotationSpec> = emptyList(), + tags: Map<KClass<*>, Any> = emptyMap() +) : TypeName(nullable, annotations, TagMap(tags)) { + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): TypeVariableName { + return copy(nullable, annotations, this.bounds, this.isReified, tags) + } + + public fun copy( + nullable: Boolean = this.isNullable, + annotations: List<AnnotationSpec> = this.annotations.toList(), + bounds: List<TypeName> = this.bounds.toList(), + reified: Boolean = this.isReified, + tags: Map<KClass<*>, Any> = this.tagMap.tags + ): TypeVariableName { + return TypeVariableName( + name, bounds.withoutImplicitBound(), variance, reified, nullable, + annotations, tags + ) + } + + private fun List<TypeName>.withoutImplicitBound(): List<TypeName> { + return if (size == 1) this else filterNot { it == NULLABLE_ANY } + } + + override fun emit(out: CodeWriter) = out.emit(name) + + public companion object { + internal fun of( + name: String, + bounds: List<TypeName>, + variance: KModifier? + ): TypeVariableName { + require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) { + "$variance is an invalid variance modifier, the only allowed values are in and out!" + } + require(bounds.isNotEmpty()) { + "$name has no bounds" + } + // Strip Any? from bounds if it is present. + return TypeVariableName(name, bounds, variance) + } + + /** Returns type variable named `name` with `variance` and without bounds. */ + @JvmStatic @JvmName("get") @JvmOverloads + public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName = + of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("get") @JvmOverloads + public operator fun invoke( + name: String, + vararg bounds: TypeName, + variance: KModifier? = null + ): TypeVariableName = + of( + name = name, + bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST), + variance = variance + ) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("get") @JvmOverloads + public operator fun invoke( + name: String, + vararg bounds: KClass<*>, + variance: KModifier? = null + ): TypeVariableName = + of( + name = name, + bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST), + variance = variance + ) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("get") @JvmOverloads + public operator fun invoke( + name: String, + vararg bounds: Type, + variance: KModifier? = null + ): TypeVariableName = + of( + name = name, + bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST), + variance = variance + ) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("get") @JvmOverloads + public operator fun invoke( + name: String, + bounds: List<TypeName>, + variance: KModifier? = null + ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("getWithClasses") @JvmOverloads + public operator fun invoke( + name: String, + bounds: Iterable<KClass<*>>, + variance: KModifier? = null + ): TypeVariableName = + of( + name, + bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST), + variance + ) + + /** Returns type variable named `name` with `variance` and `bounds`. */ + @JvmStatic @JvmName("getWithTypes") @JvmOverloads + public operator fun invoke( + name: String, + bounds: Iterable<Type>, + variance: KModifier? = null + ): TypeVariableName = + of( + name, + bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST), + variance + ) + + /** + * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid + * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a + * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables` + * map before looking up the bounds. Then if we encounter this TypeVariable again while + * constructing the bounds, we can just return it from the map. And, the code that put the entry + * in `variables` will make sure that the bounds are filled in before returning. + */ + internal fun get( + mirror: javax.lang.model.type.TypeVariable, + typeVariables: MutableMap<TypeParameterElement, TypeVariableName> + ): TypeVariableName { + val element = mirror.asElement() as TypeParameterElement + var typeVariableName: TypeVariableName? = typeVariables[element] + if (typeVariableName == null) { + // Since the bounds field is public, we need to make it an unmodifiableList. But we control + // the List that that wraps, which means we can change it before returning. + val bounds = mutableListOf<TypeName>() + val visibleBounds = Collections.unmodifiableList(bounds) + typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds) + typeVariables[element] = typeVariableName + for (typeMirror in element.bounds) { + bounds += get(typeMirror, typeVariables) + } + bounds.remove(ANY) + bounds.remove(JAVA_OBJECT) + if (bounds.isEmpty()) { + bounds.add(NULLABLE_ANY) + } + } + return typeVariableName + } + + /** Returns type variable equivalent to `type`. */ + internal fun get( + type: java.lang.reflect.TypeVariable<*>, + map: MutableMap<Type, TypeVariableName> = mutableMapOf() + ): TypeVariableName { + var result: TypeVariableName? = map[type] + if (result == null) { + val bounds = mutableListOf<TypeName>() + val visibleBounds = Collections.unmodifiableList(bounds) + result = TypeVariableName(type.name, visibleBounds) + map[type] = result + for (bound in type.bounds) { + bounds += get(bound, map) + } + bounds.remove(ANY) + bounds.remove(JAVA_OBJECT) + if (bounds.isEmpty()) { + bounds.add(NULLABLE_ANY) + } + } + return result + } + + internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY) + private val JAVA_OBJECT = ClassName("java.lang", "Object") + } +} + /** Returns type variable equivalent to `mirror`. */ @JvmName("get") public fun TypeVariable.asTypeVariableName(): TypeVariableName = diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt index f9e5c50c..08c86532 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt @@ -17,8 +17,98 @@ package com.squareup.kotlinpoet +import java.lang.reflect.Type import java.lang.reflect.WildcardType +import javax.lang.model.element.TypeParameterElement import kotlin.DeprecationLevel.WARNING +import kotlin.reflect.KClass + +public class WildcardTypeName private constructor( + outTypes: List<TypeName>, + inTypes: List<TypeName>, + nullable: Boolean = false, + annotations: List<AnnotationSpec> = emptyList(), + tags: Map<KClass<*>, Any> = emptyMap() +) : TypeName(nullable, annotations, TagMap(tags)) { + public val outTypes: List<TypeName> = outTypes.toImmutableList() + public val inTypes: List<TypeName> = inTypes.toImmutableList() + + init { + require(this.outTypes.size == 1) { "unexpected out types: $outTypes" } + } + + override fun copy( + nullable: Boolean, + annotations: List<AnnotationSpec>, + tags: Map<KClass<*>, Any> + ): WildcardTypeName { + return WildcardTypeName(outTypes, inTypes, nullable, annotations, tags) + } + + override fun emit(out: CodeWriter): CodeWriter { + return when { + inTypes.size == 1 -> out.emitCode("in %T", inTypes[0]) + outTypes == STAR.outTypes -> out.emit("*") + else -> out.emitCode("out %T", outTypes[0]) + } + } + + public companion object { + /** + * Returns a type that represents an unknown type that produces `outType`. For example, if + * `outType` is `CharSequence`, this returns `out CharSequence`. If `outType` is `Any?`, this + * returns `*`, which is shorthand for `out Any?`. + */ + @JvmStatic public fun producerOf(outType: TypeName): WildcardTypeName = + WildcardTypeName(listOf(outType), emptyList()) + + @JvmStatic public fun producerOf(outType: Type): WildcardTypeName = + producerOf(outType.asTypeName()) + + @JvmStatic public fun producerOf(outType: KClass<*>): WildcardTypeName = + producerOf(outType.asTypeName()) + + /** + * Returns a type that represents an unknown type that consumes `inType`. For example, if + * `inType` is `String`, this returns `in String`. + */ + @JvmStatic public fun consumerOf(inType: TypeName): WildcardTypeName = + WildcardTypeName(listOf(ANY), listOf(inType)) + + @JvmStatic public fun consumerOf(inType: Type): WildcardTypeName = + consumerOf(inType.asTypeName()) + + @JvmStatic public fun consumerOf(inType: KClass<*>): WildcardTypeName = + consumerOf(inType.asTypeName()) + + internal fun get( + mirror: javax.lang.model.type.WildcardType, + typeVariables: Map<TypeParameterElement, TypeVariableName> + ): TypeName { + val outType = mirror.extendsBound + if (outType == null) { + val inType = mirror.superBound + return if (inType == null) { + STAR + } else { + consumerOf(get(inType, typeVariables)) + } + } else { + return producerOf(get(outType, typeVariables)) + } + } + + internal fun get( + wildcardName: WildcardType, + map: MutableMap<Type, TypeVariableName> + ): TypeName { + return WildcardTypeName( + wildcardName.upperBounds.map { get(it, map = map) }, + wildcardName.lowerBounds.map { get(it, map = map) } + ) + } + } +} @Deprecated( message = "Mirror APIs don't give complete information on Kotlin types. Consider using" + diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt index 70e0e68b..1d9e2edb 100644 --- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt +++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt @@ -51,6 +51,10 @@ private fun jvmSuppressWildcardsAnnotation(suppress: Boolean = true) = .apply { if (!suppress) addMember("suppress = false") } .build() +public fun TypeSpec.Builder.jvmInline(): TypeSpec.Builder = addAnnotation(JvmInline::class) + +public fun TypeSpec.Builder.jvmRecord(): TypeSpec.Builder = addAnnotation(JvmRecord::class) + public fun FunSpec.Builder.jvmStatic(): FunSpec.Builder = apply { check(!name.isConstructor) { "Can't apply @JvmStatic to a constructor!" } addAnnotation(JvmStatic::class) @@ -121,7 +125,11 @@ public fun TypeName.jvmSuppressWildcards(suppress: Boolean = true): TypeName = public fun TypeName.jvmWildcard(): TypeName = copy(annotations = this.annotations + AnnotationSpec.builder(JvmWildcard::class).build()) +@Suppress("DEPRECATION") +@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`") public fun PropertySpec.Builder.jvmDefault(): PropertySpec.Builder = addAnnotation(JvmDefault::class) +@Suppress("DEPRECATION") +@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`") public fun FunSpec.Builder.jvmDefault(): FunSpec.Builder = addAnnotation(JvmDefault::class) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt index 11ec9283..b6a9114b 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt @@ -110,12 +110,12 @@ class AnnotationSpecTest { |import kotlin.Float | |@AnnotationSpecTest.HasDefaultsAnnotation( - | o = AnnotationSpecTest.Breakfast.PANCAKES, - | p = 1701, | f = 11.1, - | m = [9, 8, 1], - | l = Override::class, | j = AnnotationSpecTest.AnnotationA(), + | l = Override::class, + | m = [9, 8, 1], + | o = AnnotationSpecTest.Breakfast.PANCAKES, + | p = 1701, | q = AnnotationSpecTest.AnnotationC(value = "bar"), | r = [Float::class, Double::class] |) @@ -140,12 +140,12 @@ class AnnotationSpecTest { |import kotlin.Float | |@AnnotationSpecTest.HasDefaultsAnnotation( - | o = AnnotationSpecTest.Breakfast.PANCAKES, - | p = 1701, | f = 11.1, - | m = [9, 8, 1], - | l = Override::class, | j = AnnotationSpecTest.AnnotationA(), + | l = Override::class, + | m = [9, 8, 1], + | o = AnnotationSpecTest.Breakfast.PANCAKES, + | p = 1701, | q = AnnotationSpecTest.AnnotationC(value = "bar"), | r = [Float::class, Double::class] |) diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt index 241dfecb..99198ecd 100644 --- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt +++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt @@ -20,9 +20,11 @@ import com.google.common.truth.Truth.assertThat import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.DATA import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STRING import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName @@ -1106,4 +1108,71 @@ class JvmAnnotationsTest { |""".trimMargin() ) } + + @Test fun jvmInlineClass() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.valueClassBuilder("Taco") + .jvmInline() + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("value", STRING) + .build() + ) + .addProperty( + PropertySpec.builder("value", STRING) + .initializer("value") + .build() + ) + .build() + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + |import kotlin.jvm.JvmInline + | + |@JvmInline + |public value class Taco( + | public val `value`: String + |) + |""".trimMargin() + ) + } + + @Test fun jvmRecordClass() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Taco") + .jvmRecord() + .addModifiers(DATA) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("value", STRING) + .build() + ) + .addProperty( + PropertySpec.builder("value", STRING) + .initializer("value") + .build() + ) + .build() + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + |import kotlin.jvm.JvmRecord + | + |@JvmRecord + |public data class Taco( + | public val `value`: String + |) + |""".trimMargin() + ) + } } |