Java 애너테이션 프로세싱에서 KSP로의 참조

Java 애너테이션 프로세싱에서 KSP로의 참조

이 문서는 Java 애너테이션 프로세싱(APT) API의 기능 하나하나를 KSP로 어떻게 구현할 수 있는지 보여 주는 참조 자료예요. 표의 왼쪽은 Java의 API, 오른쪽은 그에 대응하는 KSP 요소입니다.

출처: Java annotation processing to KSP reference

본문

프로그램 요소(Program elements)

Java KSP에서 가장 가까운 기능 비고
AnnotationMirror KSAnnotation
AnnotationValue KSValueArguments
Element KSDeclaration/KSDeclarationContainer
ExecutableElement KSFunctionDeclaration
PackageElement KSFile KSP는 패키지를 프로그램 요소로 모델링하지 않아요
Parameterizable KSDeclaration
QualifiedNameable KSDeclaration
TypeElement KSClassDeclaration
TypeParameterElement KSTypeParameter
VariableElement KSValueParameter/KSPropertyDeclaration

타입(Types)

KSP는 명시적인 타입 해석을 요구해요. 그래서 Java의 일부 기능들은 해석 전에는 KSType과 그에 대응하는 요소로만 구현할 수 있습니다.

Java KSP에서 가장 가까운 기능 비고
ArrayType KSBuiltIns.arrayType
DeclaredType KSType/KSClassifierReference
ErrorType KSType.isError
ExecutableType KSType/KSCallableReference
IntersectionType KSType/KSTypeParameter
NoType KSType.isError KSP에서는 해당 없음
NullType KSP에서는 해당 없음
PrimitiveType KSBuiltIns Java의 원시 타입과 정확히 같지는 않아요
ReferenceType KSTypeReference
TypeMirror KSType
TypeVariable KSTypeParameter
UnionType 해당 없음 Kotlin은 catch 블록마다 타입이 하나뿐이에요. UnionType은 Java 애너테이션 프로세서조차 관찰할 수 없어요
WildcardType KSType/KSTypeArgument

기타(Misc)

Java KSP에서 가장 가까운 기능 비고
Name KSName
ElementKind ClassKind/FunctionKind
Modifier Modifier
NestingKind ClassKind/FunctionKind
AnnotationValueVisitor
ElementVisitor KSVisitor
AnnotatedConstruct KSAnnotated
TypeVisitor
TypeKind KSBuiltIns 일부는 builtins에서 찾고, 그 외에는 DeclaredType 확인을 위해 KSClassDeclaration을 살펴보세요
ElementFilter Collection.filterIsInstance
ElementKindVisitor KSVisitor
ElementScanner KSTopDownVisitor
SimpleAnnotationValueVisitor KSP에서는 필요 없어요
SimpleElementVisitor KSVisitor
SimpleTypeVisitor
TypeKindVisitor
Types Resolver/utils 일부 utils 기능은 심볼 인터페이스에 통합되어 있어요
Elements Resolver/utils

상세(Details)

Java 애너테이션 프로세싱 API의 기능을 KSP에서 어떻게 구현할 수 있는지 살펴볼게요.

AnnotationMirror

Java KSP 대응
getAnnotationType ksAnnotation.annotationType
getElementValues ksAnnotation.arguments

AnnotationValue

Java KSP 대응
getValue ksValueArgument.value

Element

Java KSP 대응
asType ksClassDeclaration.asType(...)KSClassDeclaration에서만 사용할 수 있어요. 타입 인자를 제공해야 합니다.
getAnnotation 구현 예정
getAnnotationMirrors ksDeclaration.annotations
getEnclosedElements ksDeclarationContainer.declarations
getEnclosingElements ksDeclaration.parentDeclaration
getKind ClassKind 또는 FunctionKind에 따라 타입을 확인하고 캐스팅해요
getModifiers ksDeclaration.modifiers
getSimpleName ksDeclaration.simpleName

ExecutableElement

Java KSP 대응
getDefaultValue 구현 예정
getParameters ksFunctionDeclaration.parameters
getReceiverType ksFunctionDeclaration.parentDeclaration
getReturnType ksFunctionDeclaration.returnType
getSimpleName ksFunctionDeclaration.simpleName
getThrownTypes Kotlin에서는 필요 없어요
getTypeParameters ksFunctionDeclaration.typeParameters
isDefault 부모 선언이 인터페이스인지 확인해요
isVarArgs ksFunctionDeclaration.parameters.any { it.isVarArg }

Parameterizable

Java KSP 대응
getTypeParameters ksFunctionDeclaration.typeParameters

QualifiedNameable

Java KSP 대응
getQualifiedName ksDeclaration.qualifiedName

TypeElement

Java KSP 대응
getEnclosedElements ksClassDeclaration.declarations
getEnclosingElement ksClassDeclaration.parentDeclaration
getInterfaces ```kotlin
// Should be able to do without resolution
ksClassDeclaration.superTypes
.map { it.resolve() }
.filter { (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE }
| `getNestingKind` | `KSClassDeclaration.parentDeclaration`와 `inner` 수정자를 확인해요 |
| `getQualifiedName` | `ksClassDeclaration.qualifiedName` |
| `getSimpleName` | `ksClassDeclaration.simpleName` |
| `getSuperclass` | ```kotlin
// Should be able to do without resolution
ksClassDeclaration.superTypes
    .map { it.resolve() }
    .filter { (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.CLASS }
``` |
| `getTypeParameters` | `ksClassDeclaration.typeParameters` |

#### TypeParameterElement

| Java | KSP 대응 |
|---|---|
| `getBounds` | `ksTypeParameter.bounds` |
| `getEnclosingElement` | `ksTypeParameter.parentDeclaration` |
| `getGenericElement` | `ksTypeParameter.parentDeclaration` |

#### VariableElement

| Java | KSP 대응 |
|---|---|
| `getConstantValue` | 구현 예정 |
| `getEnclosingElement` | `ksValueParameter.parentDeclaration` |
| `getSimpleName` | `ksValueParameter.simpleName` |

#### ArrayType

| Java | KSP 대응 |
|---|---|
| `getComponentType` | `ksType.arguments.first()` |

#### DeclaredType

| Java | KSP 대응 |
|---|---|
| `asElement` | `ksType.declaration` |
| `getEnclosingType` | `ksType.declaration.parentDeclaration` |
| `getTypeArguments` | `ksType.arguments` |

#### ExecutableType

함수에 대한 `KSType`은 그저 `FunctionN<R, T1, T2, ..., TN>` 계열로 표현되는 시그니처예요.

| Java | KSP 대응 |
|---|---|
| `getParameterTypes` | `ksType.declaration.typeParameters`, `ksFunctionDeclaration.parameters.map { it.type }` |
| `getReceiverType` | `ksFunctionDeclaration.parentDeclaration.asType(...)` |
| `getReturnType` | `ksType.declaration.typeParameters.last()` |
| `getThrownTypes` | Kotlin에서는 필요 없어요 |
| `getTypeVariables` | `ksFunctionDeclaration.typeParameters` |

#### IntersectionType

| Java | KSP 대응 |
|---|---|
| `getBounds` | `ksTypeParameter.bounds` |

#### TypeMirror

| Java | KSP 대응 |
|---|---|
| `getKind` | 원시 타입과 `Unit` 타입은 `KSBuiltIns`의 타입과 비교하고, 그 외에는 declared 타입과 비교해요 |

#### TypeVariable

| Java | KSP 대응 |
|---|---|
| `asElement` | `ksType.declaration` |
| `getLowerBound` | 결정 예정. 캡처(capture)가 제공되고 명시적 경계 검사가 필요한 경우에만 필요해요. |
| `getUpperBound` | `ksTypeParameter.bounds` |

#### WildcardType

| **Java** | **KSP 대응** |
|---|---|
| `getExtendsBound` | ```kotlin
if (ksTypeArgument.variance == Variance.COVARIANT) ksTypeArgument.type else null
``` |
| `getSuperBound` | ```kotlin
if (ksTypeArgument.variance == Variance.CONTRAVARIANT) ksTypeArgument.type else null
``` |

#### Elements

| **Java** | **KSP 대응** |
|---|---|
| `getAllAnnotationMirrors` | `KSDeclarations.annotations` |
| `getAllMembers` | `getAllFunctions`, `getAllProperties`는 구현 예정이에요 |
| `getBinaryName` | 결정 예정, Java 사양 참조 |
| `getConstantExpression` | 상수 값은 있지만 표현식은 아니에요 |
| `getDocComment` | 구현 예정 |
| `getElementValuesWithDefaults` | 구현 예정 |
| `getName` | `resolver.getKSNameFromString` |
| `getPackageElement` | 패키지는 지원되지 않아요. 패키지 정보는 얻을 수 있지만 KSP로 패키지에 대한 연산은 불가능해요 |
| `getPackageOf` | 패키지는 지원되지 않아요 |
| `getTypeElement` | `Resolver.getClassDeclarationByName` |
| `hides` | 구현 예정 |
| `isDeprecated` | ```kotlin
KsDeclaration.annotations.any { 
    it.annotationType.resolve()!!.declaration.qualifiedName!!.asString() == Deprecated::class.qualifiedName
}
``` |
| `overrides` | `KSFunctionDeclaration.overrides`/`KSPropertyDeclaration.overrides`(각 클래스의 멤버 함수) |
| `printElements` | KSP는 대부분의 클래스에 기본 `toString()` 구현이 있어요 |

#### Types

| Java | KSP 대응 |
|---|---|
| `asElement` | `ksType.declaration` |
| `asMemberOf` | `resolver.asMemberOf` |
| `boxedClass` | 필요 없어요 |
| `capture` | 결정 예정 |
| `contains` | `KSType.isAssignableFrom` |
| `directSuperTypes` | `(ksType.declaration as KSClassDeclaration).superTypes` |
| `erasure` | `ksType.starProjection()` |
| `getArrayType` | `ksBuiltIns.arrayType.replace(...)` |
| `getDeclaredType` | `ksClassDeclaration.asType` |
| `getNoType` | `ksBuiltIns.nothingType`/`null` |
| `getNullType` | 상황에 따라 `KSType.markNullable`이 유용할 수 있어요 |
| `getPrimitiveType` | 필요 없음, `KSBuiltins`을 확인해요 |
| `getWildcardType` | `KSTypeArgument`가 기대되는 곳에서 `Variance`를 사용해요 |
| `isAssignable` | `ksType.isAssignableFrom` |
| `isSameType` | `ksType.equals` |
| `isSubsignature` | `functionTypeA == functionTypeB`/`functionTypeA == functionTypeB.starProjection()` |
| `isSubtype` | `ksType.isAssignableFrom` |
| `unboxedType` | 필요 없어요 |

## 더 알아보기

- [KSP 개요](https://kotlinlang.org/docs/ksp-overview.html)
- [KSP가 Kotlin 코드를 모델링하는 방식](https://kotlinlang.org/docs/ksp-why-are-generics-so-tricky.html)
- [KSP와 Kotlin Multiplatform](https://kotlinlang.org/docs/ksp-multiplatform.html)