Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1232: Include type hint into KSErrorType. #1848

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.google.devtools.ksp.common

import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeArgument

inline fun <E> errorTypeOnInconsistentArguments(
arguments: List<KSTypeArgument>,
placeholdersProvider: () -> List<KSTypeArgument>,
withCorrectedArguments: (corrected: List<KSTypeArgument>) -> KSType,
errorType: (name: String, message: String) -> E,
): E? {
if (arguments.isNotEmpty()) {
val placeholders = placeholdersProvider()
val diff = arguments.size - placeholders.size
if (diff > 0) {
val wouldBeType = withCorrectedArguments(arguments.dropLast(diff))
return errorType(wouldBeType.toString(), "Unexpected extra $diff type argument(s)")
} else if (diff < 0) {
val wouldBeType = withCorrectedArguments(arguments + placeholders.drop(arguments.size))
return errorType(wouldBeType.toString(), "Missing ${-diff} type argument(s)")
}
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,8 @@ class ResolverImpl(
builtIns.arrayType.replace(typeArgs)
}
else -> {
getClassDeclarationByName(psiType.canonicalText)?.asStarProjectedType() ?: KSErrorType
getClassDeclarationByName(psiType.canonicalText)?.asStarProjectedType()
?: KSErrorType(psiType.canonicalText)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason to use canonical text? I think you are using simple names for implementations in analysis api?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the idea is to try including as much type information as available. If additional qualifiers are resolved by the compiler - include them. I guess you're right about the things I did in AA, so I went and tried to use qualified names there as well. See KtType.render() -> KtClassErrorType.qualifiers

}
}
}
Expand Down Expand Up @@ -1054,7 +1055,7 @@ class ResolverImpl(
}
}
// if substitution fails, fallback to the type from the property
return KSErrorType
return KSErrorType.fromReferenceBestEffort(property.type)
}

internal fun asMemberOf(
Expand Down Expand Up @@ -1247,7 +1248,7 @@ class ResolverImpl(
}

// Convert type arguments for Java wildcard, recursively.
private fun KotlinType.toWildcard(mode: TypeMappingMode): KotlinType? {
private fun KotlinType.toWildcard(mode: TypeMappingMode): Result<KotlinType> {
val parameters = constructor.parameters
val arguments = arguments

Expand All @@ -1257,20 +1258,23 @@ class ResolverImpl(
parameter.variance != org.jetbrains.kotlin.types.Variance.INVARIANT &&
argument.projectionKind != org.jetbrains.kotlin.types.Variance.INVARIANT
) {
// conflicting variances
// TODO: error message
return null
return Result.failure(
IllegalArgumentException(
"Conflicting variance: variance '${parameter.variance.label}' vs projection " +
"'${argument.projectionKind.label}'"
)
)
}

val argMode = mode.updateFromAnnotations(argument.type)
val variance = KotlinTypeMapper.getVarianceForWildcard(parameter, argument, argMode)
val genericMode = argMode.toGenericArgumentMode(
getEffectiveVariance(parameter.variance, argument.projectionKind)
)
TypeProjectionImpl(variance, argument.type.toWildcard(genericMode) ?: return null)
TypeProjectionImpl(variance, argument.type.toWildcard(genericMode).getOrElse { return Result.failure(it) })
}

return replace(wildcardArguments)
return Result.success(replace(wildcardArguments))
}

private val JVM_SUPPRESS_WILDCARDS_NAME = KSNameImpl.getCached("kotlin.jvm.JvmSuppressWildcards")
Expand Down Expand Up @@ -1369,19 +1373,24 @@ class ResolverImpl(
if (position == RefPosition.SUPER_TYPE &&
argument.projectionKind != org.jetbrains.kotlin.types.Variance.INVARIANT
) {
// Type projection isn't allowed in immediate arguments to supertypes.
// TODO: error message
return KSTypeReferenceSyntheticImpl.getCached(KSErrorType, null)
val errorType = KSErrorType(
name = type.toString(),
message = "Type projection isn't allowed in immediate arguments to supertypes"
)
return KSTypeReferenceSyntheticImpl.getCached(errorType, null)
}
}

val wildcardType = kotlinType.toWildcard(typeMappingMode)?.let {
var candidate: KotlinType = it
val wildcardType = kotlinType.toWildcard(typeMappingMode).let {
var candidate: KotlinType = it.getOrElse { error ->
val errorType = KSErrorType(name = type.toString(), message = error.message)
return KSTypeReferenceSyntheticImpl.getCached(errorType, null)
}
for (i in indexes.reversed()) {
candidate = candidate.arguments[i].type
}
getKSTypeCached(candidate)
} ?: KSErrorType
}

return KSTypeReferenceSyntheticImpl.getCached(wildcardType, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.AnnotationResolverImpl
import org.jetbrains.kotlin.resolve.BindingContext
Expand Down Expand Up @@ -161,7 +163,7 @@ private fun <T> ConstantValue<T>.toValue(parent: KSNode): Any? = when (this) {
} else classValue.classId.findKSType()
is KClassValue.Value.LocalClass -> getKSTypeCached(classValue.type)
}
is ErrorValue -> KSErrorType
is ErrorValue -> KSErrorType(toString())
is NullValue -> null
else -> value
}
Expand Down Expand Up @@ -211,7 +213,7 @@ fun LazyAnnotationDescriptor.getValueArguments(): Map<Name, ConstantValue<*>> {
} else if (resolvedArgument is DefaultValueArgument) {
valueParameter.name to DefaultConstantValue
} else {
c.annotationResolver.getAnnotationArgumentValue(c.trace, valueParameter, resolvedArgument)?.let { value ->
c.annotationResolver.getAnnotationArgumentValue(c.trace, valueParameter, resolvedArgument).let { value ->
val argExp = resolvedArgument.arguments.lastOrNull()?.getArgumentExpression()
// When some elements are not available, the expected and actual size of an array argument will
// be different. In such case, we need to reconstruct the array.
Expand All @@ -224,17 +226,22 @@ fun LazyAnnotationDescriptor.getValueArguments(): Map<Name, ConstantValue<*>> {
val bc = ResolverImpl.instance!!.bindingTrace.bindingContext
val args = argExp.innerExpressions.map {
bc.get(BindingContext.COMPILE_TIME_VALUE, it)?.toConstantValue(value.type)
?: ErrorValue.create("<ERROR VALUE>")
?: it.asErrorValue()
}
valueParameter.name to TypedArrayValue(args, value.type)
} else {
valueParameter.name to value
valueParameter.name to (value ?: argExp?.asErrorValue() ?: ErrorValue.create("ERROR VALUE"))
}
} ?: (valueParameter.name to ErrorValue.create("<ERROR VALUE>"))
}
}
}.toMap()
}

private fun KtExpression.asErrorValue(): ErrorValue {
val reprExpr = (this as? KtClassLiteralExpression)?.receiverExpression ?: this
return ErrorValue.create(reprExpr.text)
}

fun AnnotationDescriptor.createKSValueArguments(ownerAnnotation: KSAnnotation): List<KSValueArgument> {
val allValueArgs = if (this is LazyAnnotationDescriptor) {
this.getValueArguments()
Expand Down Expand Up @@ -419,14 +426,14 @@ fun ValueParameterDescriptor.getDefaultValue(ownerAnnotation: KSAnnotation): Any
if (!this.type.isError) {
defaultValue?.convert(this.type)?.toValue(ownerAnnotation)
} else {
KSErrorType
KSErrorType.fromKtErrorType(type)
}
}
}
is KtParameter -> if (!this.type.isError) {
ResolverImpl.instance!!.evaluateConstant(psi.defaultValue, this.type)?.toValue(ownerAnnotation)
} else {
KSErrorType
KSErrorType.fromKtErrorType(type)
}
is PsiAnnotationMethod -> {
when (psi.defaultValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,7 @@ class KSClassDeclarationDescriptorImpl private constructor(val descriptor: Class
}

override fun asType(typeArguments: List<KSTypeArgument>): KSType =
descriptor.defaultType.replaceTypeArguments(typeArguments)?.let {
getKSTypeCached(it, typeArguments)
} ?: KSErrorType
descriptor.defaultType.replaceTypeArguments(typeArguments)

override fun asStarProjectedType(): KSType {
return getKSTypeCached(descriptor.defaultType.replaceArgumentsWithStarProjections())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.google.devtools.ksp.symbol.impl.java

import com.google.devtools.ksp.common.errorTypeOnInconsistentArguments
import com.google.devtools.ksp.common.impl.KSNameImpl
import com.google.devtools.ksp.common.toKSModifiers
import com.google.devtools.ksp.processing.impl.KSObjectCache
Expand Down Expand Up @@ -96,8 +97,10 @@ class KSClassDeclarationJavaEnumEntryImpl private constructor(val psi: PsiEnumCo

// Enum can't have type parameters.
override fun asType(typeArguments: List<KSTypeArgument>): KSType {
if (typeArguments.isNotEmpty())
return KSErrorType
errorTypeOnInconsistentArguments(
arguments = typeArguments, placeholdersProvider = ::emptyList,
withCorrectedArguments = ::asType, errorType = ::KSErrorType,
)?.let { error -> return error }
return asStarProjectedType()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,13 @@ class KSClassDeclarationJavaImpl private constructor(val psi: PsiClass) :
}

override fun asType(typeArguments: List<KSTypeArgument>): KSType {
return descriptor?.let {
it.defaultType.replaceTypeArguments(typeArguments)?.let {
getKSTypeCached(it, typeArguments)
}
} ?: KSErrorType
return descriptor?.defaultType?.replaceTypeArguments(typeArguments) ?: KSErrorType(psi.qualifiedName)
}

override fun asStarProjectedType(): KSType {
return descriptor?.let {
getKSTypeCached(it.defaultType.replaceArgumentsWithStarProjections())
} ?: KSErrorType
} ?: KSErrorType(psi.qualifiedName)
}

override fun <D, R> accept(visitor: KSVisitor<D, R>, data: D): R {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,13 @@ class KSTypeReferenceJavaImpl private constructor(val psi: PsiType, override val
.mapNotNull {
(it.annotationType.resolve() as? KSTypeImpl)?.kotlinType?.constructor?.declarationDescriptor?.fqNameSafe
}
val resolved = if ((resolvedType.declaration as? KSClassDeclarationDescriptorImpl)
?.descriptor is NotFoundClasses.MockClassDescriptor
) {
KSErrorType
} else resolvedType
val resolved = when (val declaration = resolvedType.declaration) {
is KSClassDeclarationDescriptorImpl -> when (val descriptor = declaration.descriptor) {
is NotFoundClasses.MockClassDescriptor -> KSErrorType(descriptor.name.asString())
else -> resolvedType
}
else -> resolvedType
}
val hasNotNull = relatedAnnotations.any { it in NOT_NULL_ANNOTATIONS }
val hasNullable = relatedAnnotations.any { it in NULLABLE_ANNOTATIONS }
return if (hasNullable && !hasNotNull) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ class KSTypeReferenceLiteJavaImpl private constructor(val psiElement: PsiElement
val type: KSType by lazy {
when (psiElement) {
is PsiAnnotation -> {
val psiClass = psiElement.nameReferenceElement!!.resolve() as? PsiClass
val nameReferenceElement = psiElement.nameReferenceElement!!
val psiClass = nameReferenceElement.resolve() as? PsiClass
psiClass?.let {
(psiElement.containingFile as? PsiJavaFile)?.let {
ResolverImpl.instance!!.incrementalContext.recordLookup(it, psiClass.qualifiedName!!)
}
KSClassDeclarationJavaImpl.getCached(psiClass).asStarProjectedType()
} ?: KSErrorType
} ?: KSErrorType(nameReferenceElement.text)
}
is PsiMethod -> {
KSClassDeclarationJavaImpl.getCached(psiElement.containingClass!!).asStarProjectedType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ class KSClassDeclarationImpl private constructor(val ktClassOrObject: KtClassOrO
}

override fun asType(typeArguments: List<KSTypeArgument>): KSType {
return descriptor.defaultType.replaceTypeArguments(typeArguments)?.let {
getKSTypeCached(it, typeArguments)
} ?: KSErrorType
return descriptor.defaultType.replaceTypeArguments(typeArguments)
}

override fun asStarProjectedType(): KSType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,32 @@ package com.google.devtools.ksp.symbol.impl.kotlin

import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.symbol.impl.synthetic.KSErrorTypeClassDeclaration
import org.jetbrains.kotlin.types.FlexibleType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.error.ErrorType
import org.jetbrains.kotlin.types.error.ErrorTypeKind

object KSErrorType : KSType {
override val annotations: Sequence<KSAnnotation> = emptySequence()
class KSErrorType(
val nameHint: String?,
) : KSType {
constructor(name: String, message: String?) : this(
nameHint = listOfNotNull(name, message).takeIf { it.isNotEmpty() }?.joinToString(" % ")
)

override val arguments: List<KSTypeArgument> = emptyList()
override val annotations: Sequence<KSAnnotation>
get() = emptySequence()

override val declaration: KSDeclaration = KSErrorTypeClassDeclaration
override val arguments: List<KSTypeArgument>
get() = emptyList()

override val isError: Boolean = true
override val declaration: KSDeclaration
get() = KSErrorTypeClassDeclaration(this)

override val nullability: Nullability = Nullability.NULLABLE
override val isError: Boolean
get() = true

override val nullability: Nullability
get() = Nullability.NULLABLE

override fun isAssignableFrom(that: KSType): Boolean {
return false
Expand All @@ -51,7 +66,8 @@ object KSErrorType : KSType {
return this
}

override val isMarkedNullable: Boolean = false
override val isMarkedNullable: Boolean
get() = false

override fun replace(arguments: List<KSTypeArgument>): KSType {
return this
Expand All @@ -61,11 +77,51 @@ object KSErrorType : KSType {
return this
}

override fun toString(): String {
return "<ERROR TYPE>"
}
override fun toString(): String = nameHint?.let { "<ERROR TYPE: $it>" } ?: "<ERROR TYPE>"

override val isFunctionType: Boolean
get() = false

override val isSuspendFunctionType: Boolean
get() = false

override val isFunctionType: Boolean = false
override fun hashCode() = nameHint.hashCode()

override val isSuspendFunctionType: Boolean = false
override fun equals(other: Any?): Boolean {
return this === other || other is KSErrorType && other.nameHint == nameHint
}

companion object {
fun fromReferenceBestEffort(reference: KSTypeReference?): KSErrorType {
return when (val type = reference?.resolve()) {
is KSErrorType -> type
null -> KSErrorType(reference?.element?.toString())
else -> KSErrorType(type.toString())
}
}

fun fromKtErrorType(ktType: KotlinType): KSErrorType {
// Logic is in sync with `KotlinType.isError`
val errorType: ErrorType = when (val unwrapped = ktType.unwrap()) {
is ErrorType -> unwrapped
is FlexibleType -> unwrapped.delegate as? ErrorType
else -> null
} ?: throw IllegalArgumentException("Not an error type: $ktType")

val hint = when (errorType.kind) {
// Handle "Unresolved types" group
ErrorTypeKind.UNRESOLVED_TYPE,
ErrorTypeKind.UNRESOLVED_CLASS_TYPE,
ErrorTypeKind.UNRESOLVED_JAVA_CLASS,
ErrorTypeKind.UNRESOLVED_DECLARATION,
ErrorTypeKind.UNRESOLVED_KCLASS_CONSTANT_VALUE,
ErrorTypeKind.UNRESOLVED_TYPE_ALIAS -> errorType.formatParams.first()

// TODO: Handle more ErrorTypeKinds where it's possible to extract a name for the error type.
else -> errorType.debugMessage
}

return KSErrorType(hint)
}
}
}