K2 컴파일러 마이그레이션 가이드
K2 컴파일러 마이그레이션 가이드
Kotlin 언어와 생태계가 계속 발전하면서 Kotlin 컴파일러도 함께 발전해 왔어요. 첫 단계는 로직을 공유하는 새로운 JVM·JS IR(Intermediate Representation) 백엔드를 도입해서, 서로 다른 플랫폼의 타깃에 대한 코드 생성을 단순화한 것이었죠. 이제 그 진화의 다음 단계로, K2라고 불리는 새로운 프론트엔드가 등장했어요.
K2 컴파일러가 등장하면서 Kotlin 프론트엔드는 완전히 다시 작성됐고, 더 효율적인 새로운 아키텍처를 갖추게 됐어요. 새 컴파일러가 가져오는 근본적인 변화는 더 많은 의미 정보를 담는 하나의 통합 데이터 구조를 사용한다는 점이에요. 이 프론트엔드는 의미 분석(semantic analysis), 호출 해석(call resolution), 타입 추론(type inference)을 담당해요.
새 아키텍처와 풍부해진 데이터 구조 덕분에 K2 컴파일러는 다음과 같은 이점을 제공해요.
향상된 호출 해석과 타입 추론. 컴파일러가 더 일관되게 동작하고 여러분의 코드를 더 잘 이해해요.새 언어 기능을 위한 문법적 설탕 도입이 더 쉬워짐. 앞으로 새 기능이 도입될 때 더 간결하고 읽기 쉬운 코드를 사용할 수 있게 될 거예요.더 빠른 컴파일 시간. 컴파일 시간이 크게 빨라질 수 있어요.향상된 IDE 성능. IntelliJ IDEA와 Android Studio가 K2 컴파일러를 사용해서 Kotlin 코드를 분석해요. 안정성이 높아지고 성능도 개선되죠. 자세한 내용은 IDE 지원을 참고하세요.
이 가이드는:
- 새 K2 컴파일러의 이점을 설명해요.
- 마이그레이션할 때 부딪힐 수 있는 변화와 그에 맞게 코드를 조정하는 방법을 짚어 줘요.
- 이전 버전으로 되돌리는(roll back) 방법을 설명해요.
참고: 새 K2 컴파일러는 2.0.0부터 기본으로 활성화돼요. Kotlin 2.0.0의 새 기능과 새 K2 컴파일러에 대한 자세한 내용은 Kotlin 2.0.0의 새로운 기능 문서를 참고하세요.
성능 개선
K2 컴파일러의 성능을 평가하기 위해 우리는 Anki-Android와 Exposed라는 두 오픈소스 프로젝트에서 성능 테스트를 실행했어요. 여기서 발견한 핵심 성능 개선은 다음과 같아요.
- K2 컴파일러는 최대 94%의 컴파일 속도 향상을 가져와요. 예를 들어 Anki-Android 프로젝트에서 클린 빌드 시간이 Kotlin 1.9.23의 57.7초에서 Kotlin 2.0.0의 29.7초로 줄었어요.
- 초기화 단계는 K2 컴파일러로 최대 488% 빨라져요. 예를 들어 Anki-Android 프로젝트에서 증분 빌드의 초기화 단계가 Kotlin 1.9.23의 0.126초에서 Kotlin 2.0.0의 0.022초로 단축됐어요.
- Kotlin K2 컴파일러는 분석 단계에서 이전 컴파일러보다 최대 376% 빨라요. 예를 들어 Anki-Android 프로젝트에서 증분 빌드의 분석 시간이 Kotlin 1.9.23의 0.581초에서 Kotlin 2.0.0의 0.122초로 줄었어요.
이 개선 사항의 자세한 내용과 K2 컴파일러 성능을 분석한 방법을 더 알고 싶다면 블로그 포스트를 참고하세요.
언어 기능 개선
Kotlin K2 컴파일러는 스마트 캐스트(smart-cast)와 Kotlin Multiplatform에 관련된 언어 기능을 개선해요.
스마트 캐스트
Kotlin 컴파일러는 특정 경우에 객체를 어떤 타입으로 자동 캐스트할 수 있어요. 여러분이 직접 명시할 필요가 없죠. 이걸 스마트 캐스팅이라고 불러요. Kotlin K2 컴파일러는 이제 이전보다 더 많은 시나리오에서 스마트 캐스트를 수행해요.
Kotlin 2.0.0에서는 스마트 캐스트와 관련해 다음 영역에서 개선을 이루었어요.
지역 변수와 더 넓은 스코프
이전에는 if 조건 안에서 어떤 변수가 null이 아닌 것으로 평가되면 그 변수가 스마트 캐스트됐어요. 그러면 이 변수에 대한 정보가 if 블록의 스코프 안에서 더 공유됐죠.
하지만 if 조건 밖에서 변수를 선언하면, 그 변수에 대한 정보가 if 조건 안에서 사용될 수 없어서 스마트 캐스트가 불가능했어요. 이 동작은 when 식과 while 루프에서도 마찬가지였죠.
Kotlin 2.0.0부터는 if, when, while 조건에서 사용하기 전에 변수를 선언하면, 컴파일러가 그 변수에 대해 수집한 정보가 해당 블록에서 스마트 캐스팅에 사용될 수 있어요.
이런 것은 불리언 조건을 변수로 추출할 때 유용해요. 그러면 변수에 의미 있는 이름을 지어 주면 코드 가독성이 좋아지고, 나중에 코드에서 그 변수를 재사용할 수도 있죠. 예를 들어 이렇게요.
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
논리 or 연산자를 사용한 타입 검사
Kotlin 2.0.0에서는 객체에 대한 타입 검사를 or 연산자(||)로 결합하면, 그들의 가장 가까운 공통 상위 타입(common supertype)으로 스마트 캐스트를 수행해요. 이 변경 전에는 항상 Any 타입으로 스마트 캐스트됐었죠.
그 경우에는 객체의 프로퍼티에 접근하거나 함수를 호출하기 전에, 뒤에서 객체 타입을 수동으로 다시 확인해야 했어요. 예를 들어 이렇게요.
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
참고: 공통 상위 타입은 유니온 타입(union type)의
근사치(approximation)예요. 유니온 타입은 현재 Kotlin에서 지원되지 않아요.
인라인 함수
Kotlin 2.0.0에서 K2 컴파일러는 인라인 함수를 다르게 취급해서, 다른 컴파일러 분석과 함께 스마트 캐스트가 안전한지 판단할 수 있게 됐어요.
구체적으로 인라인 함수는 이제 암시적 callsInPlace 계약을 가진 것으로 취급돼요. 즉, 인라인 함수에 전달된 모든 람다 함수는 제자리에서(in place) 호출된다는 뜻이에요. 람다 함수가 제자리에서 호출되므로, 컴파일러는 람다 함수가 자기 함수 본문에 포함된 어떤 변수의 참조도 유출시킬 수 없다는 것을 알 수 있어요.
컴파일러는 이 지식을 다른 컴파일러 분석과 함께 사용해서, 캡처된 변수 중 어떤 것을 스마트 캐스트해도 안전한지 결정해요. 예를 들어 이렇게요.
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
함수 타입의 프로퍼티
Kotlin의 이전 버전에는 함수 타입을 가진 클래스 프로퍼티가 스마트 캐스트되지 않는 버그가 있었어요. Kotlin 2.0.0과 K2 컴파일러에서 이 동작을 고쳤어요. 예를 들어 이렇게요.
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null,
// it is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
이 변경은 invoke 연산자를 오버로드하는 경우에도 적용돼요. 예를 들어 이렇게요.
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
예외 처리
Kotlin 2.0.0에서는 스마트 캐스트 정보를 catch와 finally 블록으로 전달할 수 있도록 예외 처리를 개선했어요. 이 변경 덕분에 컴파일러가 객체가 nullable 타입인지 계속 추적하기 때문에 코드가 더 안전해져요. 예를 들어 이렇게요.
//sampleStart
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
// In Kotlin 1.9.20, the compiler says that a safe call isn't
// needed, but this is incorrect.
}
}
//sampleEnd
fun main() {
testString()
}
증가·감소 연산자
Kotlin 2.0.0 이전에는 컴파일러가 증가 또는 감소 연산자를 사용한 후 객체의 타입이 바뀔 수 있다는 것을 이해하지 못했어요. 컴파일러가 객체 타입을 정확하게 추적하지 못했기 때문에, 코드에서 해결되지 않은 참조(unresolved reference) 오류가 발생할 수 있었죠. Kotlin 2.0.0에서 이 문제가 고쳐졌어요.
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// Check if unknownObject inherits from the Tau interface
// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {
// Use the overloaded inc() operator from interface Rho.
// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()
// In Kotlin 1.9.20, the compiler doesn't perform a smart cast
// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'
// In Kotlin 1.9.20, since the compiler mistakenly thinks that
// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}
Kotlin Multiplatform
K2 컴파일러에는 Kotlin Multiplatform과 관련된 다음 영역의 개선이 있어요.
컴파일 중 공통 소스와 플랫폼 소스의 분리
이전에는 Kotlin 컴파일러의 설계상 컴파일 시점에 공통 소스셋과 플랫폼 소스셋을 분리해서 유지할 수 없었어요. 그 결과 공통 코드가 플랫폼 코드에 접근할 수 있어서, 플랫폼마다 다른 동작이 발생했죠. 게다가 공통 코드의 일부 컴파일러 설정과 의존성이 플랫폼 코드로 흘러들어가기도 했어요.
Kotlin 2.0.0에서 우리의 새 K2 컴파일러 구현은 공통 소스셋과 플랫폼 소스셋 사이의 엄격한 분리를 보장하기 위한 컴파일 체계 재설계를 포함했어요. 이 변경은 expected·actual 함수를 사용할 때 가장 두드러져요. 이전에는 공통 코드의 함수 호출이 플랫폼 코드의 함수로 해석될 수 있었죠. 예를 들어 이렇게요.
공통 코드
fun foo(x: Any) = println("common foo")
fun exampleFunction() {
foo(42)
}
플랫폼 코드
// JVM
fun foo(x: Int) = println("platform foo")
// JavaScript
// There is no foo() function overload on the JavaScript platform
이 예제에서 공통 코드는 실행되는 플랫폼에 따라 동작이 달라요.
- JVM 플랫폼에서는 공통 코드에서
foo()함수를 호출하면 플랫폼 코드의foo()함수가 호출되어platform foo로 출력돼요. - JavaScript 플랫폼에서는 공통 코드에서
foo()함수를 호출하면, 플랫폼 코드에 그런 함수가 없으므로 공통 코드의foo()함수가 호출되어common foo로 출력돼요.
Kotlin 2.0.0에서는 공통 코드가 플랫폼 코드에 접근할 수 없으므로, 두 플랫폼 모두 foo() 함수를 공통 코드의 foo() 함수로 성공적으로 해석해요: common foo.
플랫폼 간 동작 일관성 개선과 함께, 우리는 IntelliJ IDEA나 Android Studio와 컴파일러 사이에 상충되는 동작이 있었던 경우도 열심히 고쳤어요. 예를 들어 expected·actual 클래스를 사용할 때 다음과 같은 일이 벌어졌었죠.
공통 코드
expect class Identity {
fun confirmIdentity(): String
}
fun common() {
// Before 2.0.0, it triggers an IDE-only error
Identity().confirmIdentity()
// RESOLUTION_TO_CLASSIFIER : Expected class Identity has no default constructor.
}
플랫폼 코드
actual class Identity {
actual fun confirmIdentity() = "expect class fun: jvm"
}
이 예제에서 expected 클래스 Identity에는 기본 생성자가 없어서, 공통 코드에서 성공적으로 호출할 수 없어요. 이전에는 이 오류가 IDE에서만 보고됐지만 코드는 JVM에서 여전히 컴파일됐어요. 하지만 이제 컴파일러가 오류를 올바르게 보고해요.
Expected class 'expect class Identity : Any' does not have default constructor
해석 동작이 바뀌지 않는 경우 — 우리는 아직 새 컴파일 체계로 마이그레이션하는 중이라서, 같은 소스셋 안에 없는 함수를 호출할 때는 해석 동작이 여전히 동일해요. 이 차이는 주로 공통 코드에서 멀티플랫폼 라이브러리의 오버로드를 사용할 때 느낄 수 있어요.
서로 다른 시그니처를 가진 whichFun() 함수 두 개가 있는 라이브러리가 있다고 가정해 볼게요.
// Example library
// MODULE: common
fun whichFun(x: Any) = println("common function")
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
공통 코드에서 whichFun() 함수를 호출하면, 라이브러리에서 인자 타입이 가장 적절한 함수가 해석돼요.
// A project that uses the example library for the JVM target
// MODULE: common
fun main(){
whichFun(2)
// platform function
}
반대로 whichFun()의 오버로드를 같은 소스셋 안에 선언하면, 여러분의 코드가 플랫폼 전용 버전에 접근할 수 없으므로 공통 코드의 함수가 해석돼요.
// Example library isn't used
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main(){
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
멀티플랫폼 라이브러리와 비슷하게, commonTest 모듈은 별도의 소스셋에 있으므로 플랫폼 전용 코드에 여전히 접근할 수 있어요. 그래서 commonTest 모듈에서 함수 호출의 해석은 이전 컴파일 체계와 같은 동작을 보여요.
앞으로 이 남은 경우들은 새 컴파일 체계와 더 일관되게 될 거예요.
expected·actual 선언의 서로 다른 가시성 수준
Kotlin 2.0.0 이전에는 Kotlin Multiplatform 프로젝트에서 expected·actual 선언을 사용할 때 그들이 같은 가시성 수준을 가져야 했어요. Kotlin 2.0.0은 이제 서로 다른 가시성 수준도 지원하지만, 오직 actual 선언이 expected 선언보다 더 허용적일 때만 가능해요. 예를 들어 이렇게요.
expect internal class Attribute // Visibility is internal
actual class Attribute // Visibility is public by default,
// which is more permissive
마찬가지로, actual 선언에서 타입 별칭(type alias)을 사용한다면, 내부 타입(underlying type)의 가시성이 expected 선언과 같거나 더 허용적이어야 해요. 예를 들어 이렇게요.
expect internal class Attribute // Visibility is internal
internal actual typealias Attribute = Expanded
class Expanded // Visibility is public by default,
// which is more permissive
Kotlin K2 컴파일러 활성화하기
Kotlin 2.0.0부터 Kotlin K2 컴파일러는 기본으로 활성화돼요.
Kotlin 버전을 업그레이드하려면 Gradle과 Maven 빌드 스크립트에서 버전을 2.0.0 이상으로 바꾸면 돼요.
Gradle에서 Kotlin 빌드 리포트 사용하기
Kotlin 빌드 리포트는 Kotlin 컴파일러 작업의 서로 다른 컴파일 단계에 소요된 시간, 사용된 컴파일러와 Kotlin 버전, 컴파일이 증분 방식이었는지 여부에 대한 정보를 제공해요. 이 빌드 리포트는 빌드 성능을 평가하는 데 유용해요. Gradle 빌드 스캔보다 Kotlin 컴파일 파이프라인에 대한 더 많은 통찰을 제공하죠. 모든 Gradle 작업의 성능을 개괄적으로 보여 주니까요.
빌드 리포트 활성화 방법
빌드 리포트를 활성화하려면 gradle.properties 파일에서 빌드 리포트 출력을 저장할 위치를 선언해요.
kotlin.build.report.output=file
출력에 사용할 수 있는 값과 그 조합은 다음과 같아요.
| 옵션 | 설명 |
|---|---|
file |
빌드 리포트를 사람이 읽을 수 있는 형식으로 로컬 파일에 저장해요. 기본적으로 ${project_folder}/build/reports/kotlin-build/${project_name}-timestamp.txt에 저장돼요. |
single_file |
빌드 리포트를 객체 형식으로 지정된 로컬 파일에 저장해요. |
build_scan |
빌드 리포트를 빌드 스캔의 custom values 섹션에 저장해요. Gradle Enterprise 플러그인이 커스텀 값의 개수와 길이를 제한한다는 점을 유의하세요. 큰 프로젝트에서는 일부 값이 유실될 수 있어요. |
http |
HTTP(S)로 빌드 리포트를 전송해요. POST 메서드가 메트릭을 JSON 형식으로 보내요. 전송되는 데이터의 현재 버전은 Kotlin 저장소에서 볼 수 있어요. HTTP 엔드포인트 샘플은 이 블로그 포스트에서 찾을 수 있어요. |
json |
빌드 리포트를 JSON 형식으로 로컬 파일에 저장해요. 빌드 리포트 위치는 kotlin.build.report.json.directory에 설정해요. 기본 파일 이름은 ${project_name}-build-<date-time>-<index>.json이에요. |
빌드 리포트로 할 수 있는 일에 대한 자세한 내용은 빌드 리포트 문서를 참고하세요.
IDE에서의 지원
IntelliJ IDEA와 Android Studio 모두 K2 컴파일러를 완전히 지원하고, 코드 분석·코드 완성·하이라이팅을 개선하기 위해 기본적으로 사용해요. 따로 구성할 것은 없어요. 최신 버전으로 업데이트하면 그 이점을 볼 수 있어요.
Kotlin Playground에서 K2 컴파일러 시도하기
Kotlin Playground는 Kotlin 2.0.0 이상 릴리스를 지원해요. 확인해 보세요!
이전 컴파일러로 되돌리기
Kotlin 2.0.0–2.3.21에서 이전 컴파일러를 사용하려면 다음 중 하나를 하세요.
build.gradle.kts파일에서 언어 버전을1.9로 설정하세요. 또는-language-version 1.9라는 컴파일러 옵션을 사용하세요.
Kotlin 2.4.0부터는 이전 컴파일러로 되돌릴 수 없어요.
변경 사항
새 프론트엔드가 도입되면서 Kotlin 컴파일러는 여러 가지 변화를 겪었어요. 먼저 여러분의 코드에 영향을 주는 가장 중요한 변경 사항들을 짚어 보고, 무엇이 바뀌었는지 설명하고 앞으로의 모범 사례를 자세히 살펴볼게요. 더 알아보고 싶다면, 더 읽기 쉽도록 이 변경 사항들을 주제 영역별로 정리해 두었어요.
이 섹션은 다음 변경 사항들을 다룹니다.
- backing field가 있는 open 프로퍼티의 즉시 초기화
- 투영된(projected) 리시버에서 사용 중단된 합성 세터
- 접근 불가능한 제네릭 타입의 사용 금지
- 같은 이름의 Kotlin 프로퍼티와 Java 필드의 일관된 해석 순서
- Java 원시 배열의 개선된 널 안전성
- expected 클래스에서 추상 멤버에 대한 더 엄격한 규칙
backing field가 있는 open 프로퍼티의 즉시 초기화
무엇이 바뀌었나?
Kotlin 2.0에서는 backing field가 있는 모든 open 프로퍼티를 즉시 초기화해야 해요. 그렇지 않으면 컴파일 오류가 발생하죠. 이전에는 open var 프로퍼티만 즉시 초기화하면 됐지만, 이제 backing field가 있는 open val 프로퍼티까지 확장됐어요.
open class Base {
open val a: Int
open var b: Int
init {
// Error starting with Kotlin 2.0 that earlier compiled successfully
this.a = 1 //Error: open val must have initializer
// Always an error
this.b = 1 // Error: open var must have initializer
}
}
class Derived : Base() {
override val a: Int = 2
override var b = 2
}
이 변경은 컴파일러의 동작을 더 예측 가능하게 만들어요. 커스텀 세터가 있는 var 프로퍼티로 open val 프로퍼티가 오버라이드되는 예를 생각해 볼게요.
커스텀 세터를 사용하면 지연 초기화가 혼란을 일으킬 수 있어요. backing field를 초기화하려는 건지 세터를 호출하려는 건지 불분명하니까요. 과거에 세터를 호출하려고 했다면, 옛 컴파일러는 세터가 그 후 backing field를 초기화할 것이라고 보장할 수 없었어요.
이제 어떤 모범 사례가 좋을까?
우리는 항상 backing field가 있는 open 프로퍼티를 초기화하길 권장해요. 이 방식이 더 효율적이고 오류 가능성도 더 적다고 믿기 때문이에요.
하지만 프로퍼티를 즉시 초기화하고 싶지 않다면, 다음을 할 수 있어요.
- 프로퍼티를
final로 만들기 - 지연 초기화를 허용하는 private backing 프로퍼티를 사용하기
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
투영된(projected) 리시버에서 사용 중단된 합성 세터
무엇이 바뀌었나?
Java 클래스의 합성 세터(synthetic setter)를 사용해서 클래스의 투영된 타입(projected type)과 충돌하는 타입을 할당하면 오류가 발생해요.
getFoo()와 setFoo() 메서드를 가진 Container라는 Java 클래스가 있다고 가정해 볼게요.
public class Container<E> {
public E getFoo() {
return null;
}
public void setFoo(E foo) {}
}
Container 클래스의 인스턴스가 투영된 타입을 가진 다음 Kotlin 코드가 있다면, setFoo() 메서드를 사용하는 것은 항상 오류를 만들어 냈어요. 하지만 합성 foo 프로퍼티가 오류를 일으키는 것은 Kotlin 2.0.0부터예요.
fun exampleFunction(starProjected: Container<*>, inProjected: Container<in Number>, sampleString: String) {
starProjected.setFoo(sampleString)
// Error since Kotlin 1.0
// Synthetic setter `foo` is resolved to the `setFoo()` method
starProjected.foo = sampleString
// Error since Kotlin 2.0.0
inProjected.setFoo(sampleString)
// Error since Kotlin 1.0
// Synthetic setter `foo` is resolved to the `setFoo()` method
inProjected.foo = sampleString
// Error since Kotlin 2.0.0
}
이제 어떤 모범 사례가 좋을까?
이 변경으로 코드에 오류가 생긴다면, 타입 선언을 어떻게 구성할지 다시 생각해 보는 걸 권해요. 타입 투영을 사용할 필요가 없는 경우일 수도 있고, 코드에서 할당을 제거해야 할 수도 있어요.
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
접근 불가능한 제네릭 타입의 사용 금지
무엇이 바뀌었나?
새 K2 컴파일러의 아키텍처 덕분에, 우리는 접근 불가능한 제네릭 타입을 처리하는 방식을 바꿨어요. 일반적으로 코드에서 접근 불가능한 제네릭 타입에 의존하면 안 돼요. 이는 프로젝트 빌드 구성에 문제가 있어서 컴파일러가 컴파일하는 데 필요한 정보에 접근하지 못한다는 뜻이니까요. Kotlin 2.0.0에서는 접근 불가능한 제네릭 타입으로 함수 리터럴을 선언하거나 호출할 수 없고, 접근 불가능한 제네릭 타입 인자를 가진 제네릭 타입도 사용할 수 없어요. 이 제한은 나중에 코드에서 컴파일러 오류를 피하게 도와줘요.
예를 들어, 어떤 모듈에 제네릭 클래스를 선언했다고 해 보겠어요.
// Module one
class Node<V>(val value: V)
모듈 일에 대한 의존성이 구성된 다른 모듈(모듈 2)이 있다면, 여러분의 코드는 Node<V> 클래스에 접근해서 함수 타입의 타입으로 사용할 수 있어요.
// Module two
fun execute(func: (Node<Int>) -> Unit) {}
// Function compiles successfully
하지만 모듈 2에만 의존하는 세 번째 모듈(모듈 3)이 있는 식으로 프로젝트가 잘못 구성돼 있다면, Kotlin 컴파일러는 세 번째 모듈을 컴파일할 때 module one의 Node<V> 클래스에 접근할 수 없어요. 이제 모듈 3에서 Node<V> 타입을 사용하는 람다나 익명 함수는 Kotlin 2.0.0에서 오류를 일으켜요. 이렇게 해서 나중에 코드에서 피할 수 있는 컴파일러 오류, 크래시, 런타임 예외를 막아 주죠.
// Module three
fun test() {
// Triggers an error in Kotlin 2.0.0, as the type of the implicit
// lambda parameter (it) resolves to Node, which is inaccessible
execute {}
// Triggers an error in Kotlin 2.0.0, as the type of the unused
// lambda parameter (_) resolves to Node, which is inaccessible
execute { _ -> }
// Triggers an error in Kotlin 2.0.0, as the type of the unused
// anonymous function parameter (_) resolves to Node, which is inaccessible
execute(fun (_) {})
}
함수 리터럴이 접근 불가능한 제네릭 타입의 값 파라미터를 포함할 때 오류를 일으키는 것 외에도, 타입이 접근 불가능한 제네릭 타입 인자를 가질 때도 오류가 발생해요.
예를 들어 모듈 일에 같은 제네릭 클래스 선언이 있다고 해 보겠어요. 모듈 2에서 또 다른 제네릭 클래스 Container<C>를 선언하고, 추가로 Container<C>를 제네릭 클래스 Node<V>를 타입 인자로 사용하는 함수들을 모듈 2에 선언했다고 가정해 볼게요.
모듈 1
// Module one
class Node<V>(val value: V)
모듈 2
// Module two
class Container<C>(vararg val content: C)
// Functions with generic class type that
// also have a generic class type argument
fun produce(): Container<Node<Int>> = Container(Node(42))
fun consume(arg: Container<Node<Int>>) {}
모듈 3에서 이 함수들을 호출하려고 하면, 제네릭 클래스 Node<V>가 모듈 3에서 접근 불가능하므로 Kotlin 2.0.0에서 오류가 발생해요.
// Module three
fun test() {
// Triggers an error in Kotlin 2.0.0, as generic class Node<V> is
// inaccessible
consume(produce())
}
앞으로 우리는 일반적으로 접근 불가능한 타입의 사용을 계속해서 사용 중단(deprecate)할 거예요. 이미 Kotlin 2.0.0에서 비제네릭 타입을 포함한 일부 접근 불가능 타입 시나리오에 대해 경고를 추가하기 시작했어요.
예를 들어, 이전 예제와 같은 모듈 구성을 사용하되 제네릭 클래스 Node<V>를 비제네릭 클래스 IntNode로 바꾸고, 모든 함수를 모듈 2에 선언했다고 해 보겠어요.
모듈 1
// Module one
class IntNode(val value: Int)
모듈 2
// Module two
// A function that contains a lambda
// parameter with `IntNode` type
fun execute(func: (IntNode) -> Unit) {}
class Container<C>(vararg val content: C)
// Functions with generic class type
// that has `IntNode` as a type argument
fun produce(): Container<IntNode> = Container(IntNode(42))
fun consume(arg: Container<IntNode>) {}
모듈 3에서 이 함수들을 호출하면 몇 가지 경고가 발생해요.
// Module three
fun test() {
// Triggers warnings in Kotlin 2.0.0, as class IntNode is
// inaccessible.
execute {}
// Class 'IntNode' of the parameter 'it' is inaccessible.
execute { _ -> }
execute(fun (_) {})
// Class 'IntNode' of the parameter '_' is inaccessible.
// Will trigger a warning in future Kotlin releases, as IntNode is
// inaccessible.
consume(produce())
}
이제 어떤 모범 사례가 좋을까?
접근 불가능한 제네릭 타입에 대한 새 경고를 만나게 된다면, 빌드 시스템 구성에 문제가 있을 가능성이 높아요. 빌드 스크립트와 구성을 확인해 볼 것을 권장해요.
마지막 수단으로, 모듈 3이 모듈 일에 대한 직접 의존성을 구성할 수 있어요. 또는 코드를 수정해서 타입을 같은 모듈 안에서 접근 가능하게 만들 수도 있어요.
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
같은 이름의 Kotlin 프로퍼티와 Java 필드의 일관된 해석 순서
무엇이 바뀌었나?
Kotlin 2.0.0 이전에는 서로 상속하고 같은 이름의 Kotlin 프로퍼티와 Java 필드를 가진 Java·Kotlin 클래스로 작업할 때, 중복된 이름의 해석 동작이 일관되지 않았어요. IntelliJ IDEA와 컴파일러 사이에도 상충되는 동작이 있었죠. Kotlin 2.0.0의 새 해석 동작을 개발할 때 우리는 사용자에게 미치는 영향을 최소화하는 것을 목표로 했어요.
예를 들어 Base라는 Java 클래스가 있다고 해 보겠어요.
public class Base {
public String a = "a";
public String b = "b";
}
앞서 말한 Base 클래스를 상속하는 Derived라는 Kotlin 클래스도 있다고 해 보겠어요.
class Derived : Base() {
val a = "aa"
// Declares custom get() function
val b get() = "bb"
}
fun main() {
// Resolves Derived.a
println(a)
// aa
// Resolves Base.b
println(b)
// b
}
Kotlin 2.0.0 이전에는 a가 Derived Kotlin 클래스의 Kotlin 프로퍼티로 해석되는 반면, b는 Base Java 클래스의 Java 필드로 해석됐어요.
Kotlin 2.0.0에서는 예제의 해석 동작이 일관되게 되어, Kotlin 프로퍼티가 같은 이름의 Java 필드보다 우선하게 됐어요. 이제 b는 Derived.b로 해석돼요.
참고: Kotlin 2.0.0 이전에는 IntelliJ IDEA에서
a의 선언이나 사용 지점으로 이동하면, Kotlin 프로퍼티로 이동해야 하는데 Java 필드로 잘못 이동했어요. Kotlin 2.0.0부터 IntelliJ IDEA는 컴파일러와 같은 위치로 올바르게 이동해요.
일반적인 규칙은 서브클래스가 우선한다는 거예요. 이전 예제도 이를 보여 줘요. Derived 클래스가 Base Java 클래스의 서브클래스이기 때문에 Derived 클래스의 Kotlin 프로퍼티 a가 해석되니까요.
상속이 반대여서 Java 클래스가 Kotlin 클래스를 상속하는 경우에는, 일치하는 이름의 Kotlin 프로퍼티보다 서브클래스의 Java 필드가 우선해요.
이 예를 생각해 볼게요.
Kotlin
open class Base {
val a = "aa"
}
Java
public class Derived extends Base {
public String a = "a";
}
이제 다음 코드에서요.
fun main() {
// Resolves Derived.a
println(a)
// a
}
이제 어떤 모범 사례가 좋을까?
이 변경이 여러분의 코드에 영향을 준다면, 정말 중복된 이름을 사용해야 하는지 생각해 보세요. 각각 같은 이름의 필드나 프로퍼티를 가진 Java·Kotlin 클래스를 만들고, 서로 상속하게 하고 싶다면, 서브클래스의 필드나 프로퍼티가 우선한다는 점을 기억하세요.
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
Java 원시 배열의 개선된 널 안전성
무엇이 바뀌었나?
Kotlin 2.0.0부터 컴파일러는 Kotlin으로 import된 Java 원시 배열의 널 허용성(nullability)을 올바르게 추론해요. 이제 Java 원시 배열에 사용된 TYPE_USE 어노테이션으로부터 네이티브 널 허용성을 유지하고, 그 값이 어노테이션에 따라 사용되지 않으면 오류를 내보내요.
보통 @Nullable과 @NotNull 어노테이션이 있는 Java 타입을 Kotlin에서 호출하면, 그들은 적절한 네이티브 널 허용성을 받아요.
interface DataService {
@NotNull ResultContainer<@Nullable String> fetchData();
}
val dataService: DataService = ...
dataService.fetchData() // -> ResultContainer<String?>
하지만 이전에는 Java 원시 배열이 Kotlin으로 import될 때 모든 TYPE_USE 어노테이션이 유실돼서, 플랫폼 널 허용성이 되고 안전하지 않은 코드가 생길 수 있었어요.
interface DataProvider {
int @Nullable [] fetchData();
}
val dataService: DataProvider = ...
dataService.fetchData() // -> IntArray .. IntArray?
// No error, even though `dataService.fetchData()` might be `null` according to annotations
// This might result in a NullPointerException
dataService.fetchData()[0]
이 문제는 선언 자체의 널 허용성 어노테이션에는 영향을 주지 않았고, TYPE_USE 어노테이션에만 영향을 줬다는 점을 유의하세요.
이제 어떤 모범 사례가 좋을까?
Kotlin 2.0.0에서는 Java 원시 배열의 널 안전성이 Kotlin에서 표준이 됐으므로, 이들을 사용한다면 새 경고와 오류에 대비해 코드를 확인하세요.
- 명시적인 널 허용성 검사 없이
@NullableJava 원시 배열을 사용하거나, 널이 아닌 원시 배열을 기대하는 Java 메서드에null을 전달하려는 모든 코드는 이제 컴파일되지 않아요. - 널 허용성 검사와 함께
@NotNull원시 배열을 사용하면 "Unnecessary safe call" 또는 "Comparison with null always false" 경고가 발생해요.
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
expected 클래스에서 추상 멤버에 대한 더 엄격한 규칙
경고: Expected·actual 클래스는 Beta 단계예요. 거의 안정적이지만, 앞으로 마이그레이션 단계를 수행해야 할 수도 있어요. 우리는 여러분이 해야 할 추가 변경을 최소화하기 위해 최선을 다할 거예요.
무엇이 바뀌었나?
K2 컴파일러로 컴파일할 때 공통 소스와 플랫폼 소스를 분리하기 때문에, 우리는 expected 클래스에서 추상 멤버에 대한 더 엄격한 규칙을 구현했어요.
이전 컴파일러에서는 expected 비추상 클래스가 함수를 오버라이드하지 않고 추상 함수를 상속할 수 있었어요. 컴파일러가 공통 코드와 플랫폼 코드에 동시에 접근할 수 있었으므로, 추상 함수에 해당하는 오버라이드와 정의가 actual 클래스에 있는지 컴파일러가 볼 수 있었기 때문이죠.
이제 공통 소스와 플랫폼 소스가 분리되어 컴파일되므로, 상속된 함수는 함수가 추상이 아니라는 것을 컴파일러가 알 수 있도록 expected 클래스에서 명시적으로 오버라이드되어야 해요. 그렇지 않으면 컴파일러가 ABSTRACT_MEMBER_NOT_IMPLEMENTED 오류를 보고해요.
예를 들어, 추상 함수 listFiles()를 가진 FileSystem이라는 추상 클래스를 선언하는 공통 소스셋이 있다고 해 보겠어요. 플랫폼 소스셋에서 actual 선언의 일부로 listFiles() 함수를 정의했다고 가정해 볼게요.
공통 코드에서 FileSystem 클래스를 상속하는 PlatformFileSystem이라는 expected 비추상 클래스가 있다면, PlatformFileSystem 클래스는 추상 함수 listFiles()를 상속해요. 하지만 Kotlin에서 비추상 클래스에는 추상 함수를 가질 수 없어요. listFiles() 함수를 비추상으로 만들려면, abstract 키워드 없이 오버라이드로 선언해야 해요.
공통 코드
abstract class FileSystem {
abstract fun listFiles()
}
expect open class PlatformFileSystem() : FileSystem {
// In Kotlin 2.0.0, an explicit override is needed
expect override fun listFiles()
// Before Kotlin 2.0.0, an override wasn't needed
}
플랫폼 코드
actual open class PlatformFileSystem : FileSystem {
actual override fun listFiles() {}
}
이제 어떤 모범 사례가 좋을까?
expected 비추상 클래스에서 추상 함수를 상속한다면, 비추상 오버라이드를 추가하세요.
자세한 내용은 YouTrack의 해당 이슈를 참고하세요.
주제 영역별
이 주제 영역들은 여러분의 코드에 영향을 줄 가능성이 낮은 변경 사항들로, 추가 읽기를 위한 관련 YouTrack 이슈 링크를 제공해요. 이슈 ID 옆에 별표(*)가 표시된 변경 사항은 이 섹션의 시작 부분에서 설명됩니다.
타입 추론
| 이슈 ID | 제목 |
|---|---|
| KT-64189 | Incorrect type in compiled function signature of property reference if the type is Normal explicitly |
| KT-47986 | Forbid implicit inferring a type variable into an upper bound in the builder inference context |
| KT-59275 | K2: Require explicit type arguments for generic annotation calls in array literals |
| KT-53752 | Missed subtyping check for an intersection type |
| KT-59138 | Change Java type parameter based types default representation in Kotlin |
| KT-57178 | Change inferred type of prefix increment to return type of getter instead of return type of inc() operator |
| KT-57609 | K2: Stop relying on the presence of @UnsafeVariance using for contravariant parameters |
| KT-57620 | K2: Forbid resolution to subsumed members for raw types |
| KT-64641 | K2: Properly inferred type of callable reference to a callable with extension-function parameter |
| KT-57011 | Make real type of a destructuring variable consistent with explicit type when specified |
| KT-38895 | K2: Fix inconsistent behavior with integer literals overflow |
| KT-54862 | Anonymous type can be exposed from anonymous function from type argument |
| KT-22379 | Condition of while-loop with break can produce unsound smartcast |
| KT-62507 | K2: Prohibit smart cast in common code for expect/actual top-level property |
| KT-65750 | Increment and plus operators that change return type must affect smart casts |
| KT-65349 | [LC] K2: specifying variable types explicitly breaks bound smart casts in some cases that worked in K1 |
제네릭
| 이슈 ID | 제목 |
|---|---|
| KT-54309* | 투영된 리시버에서 합성 세터 사용 사용 중단 |
| KT-57600 | Forbid overriding of Java method with raw-typed parameter with generic typed parameter |
| KT-54663 | Forbid passing possibly nullable type parameter to in projected DNN parameter |
| KT-54066 | Deprecate upper bound violation in typealias constructors |
| KT-49404 | Fix type unsoundness for contravariant captured type based on Java class |
| KT-61718 | Forbid unsound code with self upper bounds and captured types |
| KT-61749 | Forbid unsound bound violation in generic inner class of generic outer class |
| KT-62923 | K2: Introduce PROJECTION_IN_IMMEDIATE_ARGUMENT_TO_SUPERTYPE for projections of outer super types of inner class |
| KT-63243 | Report MANY_IMPL_MEMBER_NOT_IMPLEMENTED when inheriting from collection of primitives with an extra specialized implementation from another supertype |
| KT-60305 | K2: Prohibit constructor call and inheritance on type alias that has variance modifiers in expanded type |
| KT-64965 | Fix type hole caused by improper handling of captured types with self-upper bounds |
| KT-64966 | Forbid generic delegating constructor calls with wrong type for generic parameter |
| KT-65712 | Report missing upper bound violation when upper bound is captured type |
해석(Resolution)
| 이슈 ID | 제목 |
|---|---|
| KT-55017* | 기본 클래스의 Java 필드와 오버로드 해석 중 파생 클래스의 Kotlin 프로퍼티 선택 |
| KT-58260 | Make invoke convention works consistently with expected desugaring |
| KT-62866 | K2: Change qualifier resolution behavior when companion object is preferred against static scope |
| KT-57750 | Report ambiguity error when resolving types and having the same-named classes star imported |
| KT-63558 | K2: migrate resolution around COMPATIBILITY_WARNING |
| KT-51194 | False negative CONFLICTING_INHERITED_MEMBERS when dependency class contained in two different versions of the same dependency |
| KT-37592 | Property invoke of a functional type with receiver is preferred over extension function invoke |
| KT-51666 | Qualified this: introduce/prioritize this qualified with type case |
| KT-54166 | Confirm unspecified behavior in case of FQ name conflicts in classpath |
| KT-64431 | K2: forbid using typealiases as qualifier in imports |
| KT-56520 | K1/K2: incorrect work of resolve tower for type references with ambiguity at lower level |
가시성(Visibility)
| 이슈 ID | 제목 |
|---|---|
| KT-64474* | 접근 불가능한 타입의 사용을 미지정 동작으로 선언 |
| KT-55179 | False negative PRIVATE_CLASS_MEMBER_FROM_INLINE on calling private class companion object member from internal inline function |
| KT-58042 | Make synthetic property invisible if equivalent getter is invisible even when overridden declaration is visible |
| KT-64255 | Forbid accessing internal setter from a derived class in another module |
| KT-33917 | Prohibit to expose anonymous types from private inline functions |
| KT-54997 | Forbid implicit non-public-API accesses from public-API inline function |
| KT-56310 | Smart casts should not affect visibility of protected members |
| KT-65494 | Forbid access to overlooked private operator functions from public inline function |
| KT-65004 | K1: Setter of var, which overrides protected val, is generates as public |
| KT-64972 | Forbid overriding by private members in link-time for Kotlin/Native |
어노테이션
| 이슈 ID | 제목 |
|---|---|
| KT-58723 | Forbid annotating statements with an annotation if it has no EXPRESSION target |
| KT-49930 | Ignore parentheses expression during REPEATED_ANNOTATION checking |
| KT-57422 | K2: Prohibit use-site 'get' targeted annotations on property getters |
| KT-46483 | Prohibit annotation on type parameter in where clause |
| KT-64299 | Companion scope is ignored for resolution of annotations on companion object |
| KT-64654 | K2: Introduced ambiguity between user and compiler-required annotations |
| KT-64527 | Annotations on enum values shouldn't be copied to enum value classes |
| KT-63389 | K2: WRONG_ANNOTATION_TARGET is reported on incompatible annotations of a type wrapped into ()? |
| KT-63388 | K2: WRONG_ANNOTATION_TARGET is reported on catch parameter type's annotations |
널 안전성
| 이슈 ID | 제목 |
|---|---|
| KT-54521* | Java에서 Nullable로 어노테이션된 배열 타입의 안전하지 않은 사용 사용 중단 |
| KT-41034 | K2: Change evaluation semantics for combination of safe calls and convention operators |
| KT-50850 | Order of supertypes defines nullability parameters of inherited functions |
| KT-53982 | Keep nullability when approximating local types in public signatures |
| KT-62998 | Forbid assignment of a nullable to a not-null Java field as a selector of unsafe assignment |
| KT-63209 | Report missing errors for error-level nullable arguments of warning-level Java types |
Java 상호운용성
| 이슈 ID | 제목 |
|---|---|
| KT-53061 | Forbid Java and Kotlin classes with the same FQ name in sources |
| KT-49882 | Classes inherited from Java collections have inconsistent behavior depending on order of supertypes |
| KT-66324 | K2: unspecified behavior in case of Java class inheritance from a Kotlin private class |
| KT-66220 | Passing java vararg method to inline function leads to array of arrays in runtime instead of just an array |
| KT-66204 | Allow to override internal members in K-J-K hierarchy |
프로퍼티
| 이슈 ID | 제목 |
|---|---|
| KT-57555* | [LC] backing field가 있는 open 프로퍼티의 지연 초기화 금지 |
| KT-58589 | Deprecate missed MUST_BE_INITIALIZED when no primary constructor is presented or when class is local |
| KT-64295 | Forbid recursive resolve in case of potential invoke calls on properties |
| KT-57290 | Deprecate smart cast on base class property from invisible derived class if base class is from another module |
| KT-62661 | K2: Missed OPT_IN_USAGE_ERROR for data class properties |
제어 흐름
| 이슈 ID | 제목 |
|---|---|
| KT-56408 | Inconsistent rules of CFA in class initialization block between K1 and K2 |
| KT-57871 | K1/K2 inconsistency on if-conditional without else-branch in parenthesis |
| KT-42995 | False negative "VAL_REASSIGNMENT" in try/catch block with initialization in scope function |
| KT-65724 | Propagate data flow information from try block to catch and finally blocks |
Enum 클래스
| 이슈 ID | 제목 |
|---|---|
| KT-57608 | Prohibit access to the companion object of enum class during initialization of enum entry |
| KT-34372 | Report missed error for virtual inline method in enum classes |
| KT-52802 | Report ambiguity resolving between property/field and enum entry |
| KT-47310 | Change qualifier resolution behavior when companion property is preferred against enum entry |
함수형(SAM) 인터페이스
| 이슈 ID | 제목 |
|---|---|
| KT-52628 | Deprecate SAM constructor usages which require OptIn without annotation |
| KT-57014 | Prohibit returning values with incorrect nullability from lambda for SAM constructor of JDK function interfaces |
| KT-64342 | SAM conversion of parameter types of callable references leads to CCE |
컴패니언 객체
| 이슈 ID | 제목 |
|---|---|
| KT-54316 | Out-of-call reference to companion object's member has invalid signature |
| KT-47313 | Change (V)::foo reference resolution when V has a companion |
기타
| 이슈 ID | 제목 |
|---|---|
| KT-59739* | K2/MPP reports [ABSTRACT_MEMBER_NOT_IMPLEMENTED] for inheritor in common code when the implementation is located in the actual counterpart |
| KT-49015 | Qualified this: change behavior in case of potential label conflicts |
| KT-56545 | Fix incorrect functions mangling in JVM backend in case of accidental clashing overload in a Java subclass |
| KT-62019 | [LC issue] Prohibit suspend-marked anonymous function declarations in statement positions |
| KT-55111 | OptIn: forbid constructor calls with default arguments (parameters with default values) under marker |
| KT-61182 | Unit conversion is accidentally allowed to be used for expressions on variables + invoke resolution |
| KT-55199 | Forbid promoting callable references with adaptations to KFunction |
| KT-65776 | [LC] K2 breaks false && ... and `false |
| KT-65682 | [LC] Deprecate header/impl keywords |
| KT-45375 | Generate all Kotlin lambdas via invokedynamic + LambdaMetafactory by default |
Kotlin 릴리스와의 호환성
다음 Kotlin 릴리스는 새 K2 컴파일러를 지원해요.
| Kotlin 릴리스 | 안정성 수준 |
|---|---|
| 2.0.0–2.4.20 | Stable |
| 1.9.20–1.9.25 | Beta |
| 1.9.0–1.9.10 | JVM은 Beta |
| 1.7.0–1.8.22 | Alpha |
Kotlin 라이브러리와의 호환성
Kotlin/JVM으로 작업한다면, K2 컴파일러는 어떤 Kotlin 버전으로 컴파일된 라이브러리와도 함께 동작해요.
Kotlin Multiplatform으로 작업한다면, K2 컴파일러는 Kotlin 버전 1.9.20 이상으로 컴파일된 라이브러리와 함께 동작하는 것이 보장돼요.
컴파일러 플러그인 지원
현재 Kotlin K2 컴파일러는 다음 Kotlin 컴파일러 플러그인을 지원해요.
all-open- AtomicFU
jvm-abi-genjs-plain-objects- kapt
- Lombok
no-arg- Parcelize
- Power-assert
- SAM with receiver
- Serialization
또한 Kotlin K2 컴파일러는 다음을 지원해요.
- Jetpack Compose 1.5.0 이상 버전의 컴파일러 플러그인
- KSP2 이후의 Kotlin Symbol Processing (KSP)
팁: 추가 컴파일러 플러그인을 사용한다면, 그 문서에서 K2와 호환되는지 확인해 보세요.
커스텀 컴파일러 플러그인 업그레이드하기
경고: 커스텀 컴파일러 플러그인은 Experimental인 플러그인 API를 사용해요. 따라서 API가 언제든 바뀔 수 있으므로 하위 호환성을 보장할 수 없어요.
업그레이드 과정은 가진 커스텀 플러그인의 유형에 따라 두 가지 경로가 있어요.
백엔드 전용 컴파일러 플러그인
플러그인이 IrGenerationExtension 확장 지점만 구현한다면, 과정은 다른 새 컴파일러 릴리스와 동일해요. 사용하는 API에 변경 사항이 있는지 확인하고 필요하면 수정하면 돼요.
백엔드 및 프론트엔드 컴파일러 플러그인
플러그인이 프론트엔드 관련 확장 지점을 사용한다면, 새 K2 컴파일러 API를 사용해서 플러그인을 다시 작성해야 해요. 새 API에 대한 소개는 FIR Plugin API를 참고하세요.
참고: 커스텀 컴파일러 플러그인 업그레이드에 질문이 있다면 #compiler Slack 채널에 참여하세요. 최선을 다해 도와드릴게요.
새 K2 컴파일러에 대한 피드백 공유
어떤 피드백이든 감사히 받겠습니다!
- 새 K2 컴파일러로 마이그레이션하면서 겪는 문제를 이슈 트래커에 보고해 주세요.