Types
Last modified: 01 August 2024KaType
is a fundamental concept in the Analysis API, representing types of Kotlin expressions and declarations. It provides information about the type structure, nullability, and annotations.
KaType Hierarchy
The KaType
interface serves as the base for specific type kinds:
KaClassType
: Represents types of classes, objects, and interfaces. This includes both named class types (e.g.,String
,Int
) and types of anonymous objects. For function types like(Int) -> String
, there is aKaFunctionType
subtype.KaTypeParameterType
: Represents type parameter types, such asT
inclass Box<T>()
.KaCapturedType
: Represents captured types.KaDefinitelyNotNullType
: Represents aT & Any
type.KaFlexibleType
: Represents types with both a lower and upper bound, such asString?
or(Mutable)List<Int>
.KaIntersectionType
: Represents types that are intersections of other types.KaDynamicType
: Represents dynamic types, used for interoperability with dynamically typed languages (e.g., JavaScript).KaErrorType
: Represents unresolved types, providing information about the error.
Example
The following example demonstrates how to obtain the KaType
of a KtExpression
and render its type:
val expression: KtExpression = ...
analyze(expression) {
val type = expression.expressionType
if (type is KaClassType) {
// Analyze the class type
val className = type.classId.asSingleFqName().asString()
println(className)
}
}
Avoid using FqName
s or raw strings for type comparison. Use ClassId
s instead:
val MY_CLASS_ID = ClassId.fromString("my/app/MyClass")
fun check(expression: KtExpression): Boolean {
analyze(expression) {
val type = expression.expressionType
return type is KaClassType && type.isClassType(MY_CLASS_ID)
}
}
Getting a KaType
You have already seen how to get the expression type. Here is the complete list of utilities that convert either PSI or symbols to types:
val KtExpression.expressionType: KaType?
The expression type, or
null
if the given expression does not contribute a value.Particularly, the method returns:
A not-null type for valued expressions (e.g., a variable, a function call, a lambda expression);
Unit
for statements (e.g., assignments, loops);null
forKtExpression
s that are not a part of the expression tree (e.g., expressions in import or package statements).
note
The Analysis API distinguishes between an expression's type and its expected type, which represent different aspects of the Kotlin type system.
The expression type represents the actual type of an expression after it has been resolved. It reflects the result of type inference, smart casts, and implicit conversions.
The expected type represents the type that is expected for an expression at a specific location in the code. This is determined by the context in which the expression appears, such as a variable type for its initializer, or a parameter type for a function call.
val KtDeclaration.returnType: KaType
The return type of the given
KtDeclaration
.Note: For
vararg foo: T
parameter returns fullArray<out T>
type (unlikeKaValueParameterSymbol.returnType
, which returnsT
).val KtFunction.functionType: KaType
The functional type of the given
KtFunction
.Depending on the function's attributes, such as
suspend
or reflective access, different functional type, such asSuspendFunction
,KFunction
, orKSuspendFunction
, will be constructed.
Comparing KaType
s
Equivalence check
To check if two types are equivalent, use semanticallyEquals()
:
val type1: KaType = ...
val type2: KaType = ...
val areEqual = type1.semanticallyEquals(type2)
Comparing to type1 == type2
, which only considers simple structural equivalence, semanticallyEquals()
ensures that type1
can be substituted with type2
.
Subtyping check
To determine if one type is a subtype of another, use isSubtypeOf
:
val subtype: KaType = ...
val supertype: KaType = ...
val isSubtype = subtype.isSubtypeOf(supertype)
Building KaType
s
The Analysis API provides facilities for constructing KaType
instances, representing various Kotlin types. Here's how you can build different types:
Building Class Types
To build a class type, use the buildClassType
function. You can specify the class either by its ClassId
or using a KaClassLikeSymbol
:
By a class name:
analyze(ktFile) {
val intType = buildClassType(DefaultTypeClassIds.INT)
}
By a class symbol:
analyze(ktFile) {
val listSymbol: KaClassLikeSymbol = ...
val listOfStringType = buildClassType(listSymbol) {
argument(builtinTypes.STRING)
}
}
note
If the provided
ClassId
doesn't resolve to a valid symbol, thebuildClassType
function will return an error type.
Specifying Type Arguments:
Within the buildClassType
block, you can specify type arguments using the argument
function. This function accepts either a KaTypeProjection
or a KaType
and optionally its variance:
analyze(ktFile) {
val mapType = buildClassType(DefaultTypeClassIds.MAP) {
argument(builtinTypes.INT, Variance.IN_VARIANCE)
argument(builtinTypes.STRING, Variance.OUT_VARIANCE)
}
}
Nullability:
You can also set the nullability of the constructed type by modifying the nullability
property within the builder:
analyze(ktFile) {
val nullableStringType = buildClassType(DefaultTypeClassIds.STRING) {
nullability = KaTypeNullability.NULLABLE
}
}
Building Type Parameter Types
To build a type parameter type, use the buildTypeParameterType
function. You need to provide the corresponding KaTypeParameterSymbol
:
analyze(ktFile) {
val typeParameterSymbol: KaTypeParameterSymbol = ...
val type = buildTypeParameterType(typeParameterSymbol)
}
Note: Similar to class types, you can adjust the nullability of the type parameter type within the builder.