Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 54 additions & 29 deletions api/src/main/kotlin/com/google/devtools/ksp/utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,25 @@ fun KSNode.validate(
}

/** Find the KSClassDeclaration that the alias points to, recursively. */
fun KSTypeAlias.findActualType(): KSClassDeclaration {
val resolvedType = this.type.resolve().declaration
return if (resolvedType is KSTypeAlias) {
resolvedType.findActualType()
} else {
resolvedType as KSClassDeclaration
fun KSTypeAlias.findActualType(): KSClassDeclaration = expandTypeAlias(mutableSetOf())

private fun KSTypeAlias.expandTypeAlias(visited: MutableSet<KSDeclaration>): KSClassDeclaration {
if (!visited.add(this)) {
throw InternalKSPException(
"Circular type alias declaration: '${qualifiedName?.asString()}'",
Comment thread
jaschdoc marked this conversation as resolved.
Resolved
this.location,
this.javaClass
)
}

return when (val resolved = type.resolve().declaration) {
is KSTypeAlias -> resolved.expandTypeAlias(visited)
is KSClassDeclaration -> resolved
else -> throw InternalKSPException(
"Type alias expanded to unknown declaration: '$resolved'",
resolved.location,
resolved.javaClass
)
}
}

Expand Down Expand Up @@ -225,10 +238,22 @@ internal fun KSDeclaration.isKotlinBackingField(): Boolean =
* get all super types for a class declaration Calling [getAllSuperTypes] requires type resolution
* therefore is expensive and should be avoided if possible.
*/
fun KSClassDeclaration.getAllSuperTypes(): Sequence<KSType> {
fun KSClassDeclaration.getAllSuperTypes(): Sequence<KSType> =
Sequence { getAllSuperTypes(mutableSetOf(), mutableSetOf()).iterator() }

private fun KSClassDeclaration.getAllSuperTypes(
visitedClasses: MutableSet<KSClassDeclaration>,
visitedParams: MutableSet<KSTypeParameter>,
): Sequence<KSType> {
if (!visitedClasses.add(this)) {
return emptySequence()
}

fun KSTypeParameter.getTypesUpperBound(): Sequence<KSClassDeclaration> =
this.bounds.flatMap {
fun KSTypeParameter.getTypesUpperBound(): Sequence<KSClassDeclaration> {
if (!visitedParams.add(this)) {
return emptySequence()
}
return bounds.flatMap {
when (val resolvedDeclaration = it.resolve().declaration) {
is KSClassDeclaration -> sequenceOf(resolvedDeclaration)
is KSTypeAlias -> sequenceOf(resolvedDeclaration.findActualType())
Expand All @@ -241,29 +266,29 @@ fun KSClassDeclaration.getAllSuperTypes(): Sequence<KSType> {
)
}
}
}

return this.superTypes
.map { it.resolve() }
.plus(
this.superTypes
.map { it.resolve().declaration }
fun findClassDeclarations(declaration: KSDeclaration): Sequence<KSClassDeclaration> = when (declaration) {
is KSClassDeclaration -> sequenceOf(declaration)
is KSTypeAlias -> sequenceOf(declaration.findActualType())
is KSTypeParameter -> declaration.getTypesUpperBound()
else -> throw InternalKSPException(
"Unhandled super type kind",
declaration.location,
declaration.javaClass,
)
}

val immediateSuperTypes = superTypes.map { it.resolve() }
val transitiveSuperTypes = immediateSuperTypes
.flatMap { type ->
findClassDeclarations(type.declaration)
.flatMap {
when (it) {
is KSClassDeclaration -> it.getAllSuperTypes()
is KSTypeAlias -> it.findActualType().getAllSuperTypes()
is KSTypeParameter ->
it.getTypesUpperBound().flatMap { it.getAllSuperTypes() }

else ->
throw InternalKSPException(
"Unhandled super type kind",
it.location,
it.javaClass,
)
}
it.getAllSuperTypes(visitedClasses, visitedParams)
}
)
.distinct()
Comment thread
jaschdoc marked this conversation as resolved.
Resolved
}

return (immediateSuperTypes + transitiveSuperTypes).distinct()
}

fun KSClassDeclaration.isAbstract() =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.ksp.processor

import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated

class AllSuperTypesProcessor(val classNames: List<String>, override val enableNewFeatures: Boolean) :
AbstractTestProcessor() {
private val results: MutableList<String> = mutableListOf()

override fun toResult(): List<String> {
return results
}

override fun process(resolver: Resolver): List<KSAnnotated> {
classNames.forEach { className ->
resolver.getClassDeclarationByName(className)
?.getAllSuperTypes()
?.forEach { type ->
results.add(type.declaration.qualifiedName!!.asString())
}
}
return emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.ksp.processor

import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.intellij.util.containers.addIfNotNull

/**
* This test asserts that [getAllSuperTypes] is stateless/referentially transparent,
* i.e. reusing the same sequence gives equal output:
* ```
* val superTypes = ...getAllSuperTypes()
* superTypes.terminalOperation() == superTypes.terminalOperation()
* ```
*/
class AllSuperTypesRepeatableProcessor(val classNames: List<String>, override val enableNewFeatures: Boolean) :
AbstractTestProcessor() {
private val results: MutableList<String> = mutableListOf()

override fun toResult(): List<String> {
return results
}

override fun process(resolver: Resolver): List<KSAnnotated> {
classNames.forEach { className ->
val superTypes = resolver.getClassDeclarationByName(className)?.getAllSuperTypes() ?: return@forEach
results.add("Original:")
superTypes.mapNotNull { it.declaration.qualifiedName?.asString() }.forEach(results::addIfNotNull)

results.add("Copy:")
superTypes.mapNotNull { it.declaration.qualifiedName?.asString() }.forEach(results::addIfNotNull)

// Apply terminal operation that does not consume the entire sequence.
superTypes.any { it.declaration.qualifiedName?.asString() == "L03b" }

results.add("After Partial:")
superTypes.mapNotNull { it.declaration.qualifiedName?.asString() }.forEach(results::addIfNotNull)
}
return emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,18 @@ abstract class KSPUnitTestSuite(
runTest("$AA_PATH/jvmModifiersCompanionObject.kt")
}

@TestMetadata("diamondInheritance.kt")
@Test
fun testDiamondInheritance() {
runTest("$AA_PATH/diamondInheritance.kt")
}

@TestMetadata("diamondInheritanceRepeatable.kt")
@Test
fun testDiamondInheritanceRepeatable() {
runTest("$AA_PATH/diamondInheritanceRepeatable.kt")
}

@TestMetadata("lateinitProperties.kt")
@Test
fun testLateinitProperties() {
Expand Down
119 changes: 119 additions & 0 deletions kotlin-analysis-api/testData/diamondInheritance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// TEST PROCESSOR: AllSuperTypesProcessor
// PROCESSOR INPUT: Leaf
// EXPECTED:
// L30a
// L30b
// L29a
// L29b
// L28a
// L28b
// L27a
// L27b
// L26a
// L26b
// L25a
// L25b
// L24a
// L24b
// L23a
// L23b
// L22a
// L22b
// L21a
// L21b
// L20a
// L20b
// L19a
// L19b
// L18a
// L18b
// L17a
// L17b
// L16a
// L16b
// L15a
// L15b
// L14a
// L14b
// L13a
// L13b
// L12a
// L12b
// L11a
// L11b
// L10a
// L10b
// L09a
// L09b
// L08a
// L08b
// L07a
// L07b
// L06a
// L06b
// L05a
// L05b
// L04a
// L04b
// L03a
// L03b
// L02a
// L02b
// L01a
// L01b
// T00
// kotlin.Any
// END

// FILE: main.kt

interface T00
interface L01a : T00; interface L01b : T00
interface L02a : L01a, L01b; interface L02b : L01a, L01b
interface L03a : L02a, L02b; interface L03b : L02a, L02b
interface L04a : L03a, L03b; interface L04b : L03a, L03b
interface L05a : L04a, L04b; interface L05b : L04a, L04b
interface L06a : L05a, L05b; interface L06b : L05a, L05b
interface L07a : L06a, L06b; interface L07b : L06a, L06b
interface L08a : L07a, L07b; interface L08b : L07a, L07b
interface L09a : L08a, L08b; interface L09b : L08a, L08b
interface L10a : L09a, L09b; interface L10b : L09a, L09b
interface L11a : L10a, L10b; interface L11b : L10a, L10b
interface L12a : L11a, L11b; interface L12b : L11a, L11b
interface L13a : L12a, L12b; interface L13b : L12a, L12b
interface L14a : L13a, L13b; interface L14b : L13a, L13b
interface L15a : L14a, L14b; interface L15b : L14a, L14b
interface L16a : L15a, L15b; interface L16b : L15a, L15b
interface L17a : L16a, L16b; interface L17b : L16a, L16b
interface L18a : L17a, L17b; interface L18b : L17a, L17b
interface L19a : L18a, L18b; interface L19b : L18a, L18b
interface L20a : L19a, L19b; interface L20b : L19a, L19b
interface L21a : L20a, L20b; interface L21b : L20a, L20b
interface L22a : L21a, L21b; interface L22b : L21a, L21b
interface L23a : L22a, L22b; interface L23b : L22a, L22b
interface L24a : L23a, L23b; interface L24b : L23a, L23b
interface L25a : L24a, L24b; interface L25b : L24a, L24b
interface L26a : L25a, L25b; interface L26b : L25a, L25b
interface L27a : L26a, L26b; interface L27b : L26a, L26b
interface L28a : L27a, L27b; interface L28b : L27a, L27b
interface L29a : L28a, L28b; interface L29b : L28a, L28b
interface L30a : L29a, L29b; interface L30b : L29a, L29b

abstract class Leaf : L30a, L30b
Loading
Loading