KSP가 Kotlin 코드를 모델링하는 방식
KSP가 Kotlin 코드를 모델링하는 방식 (How KSP models Kotlin code)
API 정의는 KSP GitHub 저장소에서 찾을 수 있어요. 아래 다이어그램은 KSP에서 Kotlin이 어떻게 모델링되는지 개요를 보여줘요.
출처: Kotlin 공식 문서
본문
타입과 해석 (Type and resolution)
해석(resolution)이 밑바탕에 깔린 API 구현 비용의 대부분을 차지해요. 그래서 타입 참조는 프로세서가 명시적으로 해석하도록 설계되어 있어요(몇 가지 예외는 있죠). 타입(KSFunctionDeclaration.returnType이나 KSAnnotation.annotationType 같은 것)을 참조할 때는 항상 KSTypeReference가 나와요. KSTypeReference는 애노테이션과 수정자(modifiers)를 가진 KSReferenceElement예요.
interface KSFunctionDeclaration : ... {
val returnType: KSTypeReference?
// ...
}
interface KSTypeReference : KSAnnotated, KSModifierListOwner {
val type: KSReferenceElement
}
KSTypeReference는 Kotlin의 타입 체계 안에 있는 타입을 가리키는 KSType으로 해석할 수 있어요.
KSTypeReference는 KSReferenceElement를 갖는데, 이 요소가 Kotlin의 프로그램 구조, 즉 그 참조가 어떻게 쓰였는지를 모델링해요. Kotlin 문법의 type 요소에 대응하는 개념이에요.
KSReferenceElement는 KSClassifierReference나 KSCallableReference일 수 있는데, 둘 다 해석을 하지 않아도 유용한 정보를 많이 담고 있어요. 예를 들어 KSClassifierReference는 referencedName을 갖고 있고, KSCallableReference는 receiverType, functionArguments, returnType을 갖고 있어요.
KSTypeReference가 가리키는 원래 선언이 필요하다면, 보통 KSType으로 해석한 뒤 KSType.declaration으로 접근해서 찾을 수 있어요. 타입이 언급된 곳에서 그 클래스가 정의된 곳으로 이동하는 모습은 이렇게 생겼어요.
val ksType: KSType = ksTypeReference.resolve()
val ksDeclaration: KSDeclaration = ksType.declaration
타입 해석은 비용이 크기 때문에 명시적인 형태를 갖고 있어요. 해석에서 얻는 정보 중 일부는 KSReferenceElement에 이미 들어 있기도 해요. 예를 들어 KSClassifierReference.referencedName은 관심 없는 요소를 많이 걸러낼 수 있어요. KSDeclaration이나 KSType에서 특정 정보가 필요할 때만 타입을 해석하면 돼요.
함수 타입을 가리키는 KSTypeReference는 대부분의 정보를 이미 그 요소 안에 갖고 있어요. Function0, Function1 같은 계열로 해석할 수는 있지만, 그렇게 해석해도 KSCallableReference가 주는 정보보다 더 나오는 건 없어요. 함수 타입 참조를 해석하는 게 유용한 경우는 함수 프로토타입의 정체성을 다룰 때 정도예요.