[FIR] Add ability to generate members and nested classifiers for generated classes

This commit is contained in:
Dmitriy Novozhilov
2021-09-24 16:54:21 +03:00
committed by TeamCityServer
parent 270962e176
commit f3a9d70eb6
22 changed files with 515 additions and 183 deletions
@@ -19,6 +19,7 @@ class FirAllOpenComponentRegistrar : FirExtensionRegistrar() {
// +::AllOpenNestedClassGenerator
+::AllOpenAdditionalCheckers
+::AllOpenTopLevelDeclarationsGenerator
+::AllOpenClassGenerator
// +::AllOpenRecursiveNestedClassGenerator
}
}
@@ -0,0 +1,202 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.plugin.generators
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.builder.buildPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.builder.buildRegularClass
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunction
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
import org.jetbrains.kotlin.fir.extensions.predicate.DeclarationPredicate
import org.jetbrains.kotlin.fir.extensions.predicate.has
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.plugin.fqn
import org.jetbrains.kotlin.fir.resolve.symbolProvider
import org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
/*
* Generates class /foo.AllOpenGenerated with
* - empty public constructor
* - testClassName() functions for all classes annotated with @B
* - NestedClassName nested classes for all classes annotated with @B
* - function `materialize: ClassName` in those nested classes
*/
class AllOpenClassGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {
companion object {
private val FOO_PACKAGE = FqName.topLevel(Name.identifier("foo"))
private val GENERATED_CLASS_ID = ClassId(FOO_PACKAGE, Name.identifier("AllOpenGenerated"))
private val MATERIALIZE_NAME = Name.identifier("materialize")
}
object Key : FirPluginKey() {
override fun toString(): String {
return "AllOpenClassGeneratorKey"
}
}
private val predicateBasedProvider = session.predicateBasedProvider
private val matchedClasses by lazy {
predicateBasedProvider.getSymbolsByPredicate(predicate).map { it.symbol }.filterIsInstance<FirRegularClassSymbol>()
}
private val classIdsForMatchedClasses: Map<ClassId, FirRegularClassSymbol> by lazy {
matchedClasses.associateBy {
GENERATED_CLASS_ID.createNestedClassId(Name.identifier("Nested${it.classId.shortClassName}"))
}
}
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol<*>? {
val owner = classId.outerClassId?.let { session.symbolProvider.getClassLikeSymbolByClassId(it) } as? FirClassSymbol<*>
return when {
owner != null -> when (val origin = owner.origin) {
is FirDeclarationOrigin.Plugin -> when (origin.key) {
Key -> generateNestedClass(classId, owner)
else -> null
}
else -> null
}
else -> generateAllOpenGeneratedClass(classId)
}
}
private fun generateAllOpenGeneratedClass(classId: ClassId): FirClassLikeSymbol<*>? {
if (classId != GENERATED_CLASS_ID) return null
return buildClass(classId).symbol
}
override fun generateConstructors(callableId: CallableId): List<FirConstructorSymbol> {
val classId = when {
callableId.isGeneratedConstructor -> GENERATED_CLASS_ID
callableId.isNestedConstructor -> GENERATED_CLASS_ID.createNestedClassId(callableId.callableName)
else -> return emptyList()
}
val constructor = buildPrimaryConstructor {
moduleData = session.moduleData
origin = key.origin
returnTypeRef = buildResolvedTypeRef {
type = ConeClassLikeTypeImpl(
ConeClassLikeLookupTagImpl(classId),
emptyArray(),
isNullable = false
)
}
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
)
symbol = FirConstructorSymbol(callableId)
}
return listOf(constructor.symbol)
}
private val CallableId.isGeneratedConstructor: Boolean
get() {
if (classId != null) return false
if (packageName != FOO_PACKAGE) return false
return callableName == GENERATED_CLASS_ID.shortClassName
}
private val CallableId.isNestedConstructor: Boolean
get() {
if (classId != GENERATED_CLASS_ID) return false
return classIdsForMatchedClasses.keys.any { it.shortClassName == callableName }
}
private fun generateNestedClass(classId: ClassId, owner: FirClassSymbol<*>): FirClassLikeSymbol<*>? {
if (owner.classId != GENERATED_CLASS_ID) return null
val matchedClass = classIdsForMatchedClasses[classId] ?: return null
return buildClass(classId).also {
it.matchedClass = matchedClass.classId
}.symbol
}
@OptIn(SymbolInternals::class)
override fun generateFunctions(callableId: CallableId, owner: FirClassSymbol<*>?): List<FirNamedFunctionSymbol> {
if (callableId.classId !in classIdsForMatchedClasses || callableId.callableName != MATERIALIZE_NAME) return emptyList()
require(owner is FirRegularClassSymbol)
val matchedClassId = owner.fir.matchedClass ?: return emptyList()
val matchedClassSymbol = session.symbolProvider.getClassLikeSymbolByClassId(matchedClassId) ?: return emptyList()
val function = buildSimpleFunction {
moduleData = session.moduleData
origin = key.origin
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
)
returnTypeRef = buildResolvedTypeRef {
type = ConeClassLikeTypeImpl(
matchedClassSymbol.toLookupTag(),
emptyArray(),
isNullable = false
)
}
name = MATERIALIZE_NAME
symbol = FirNamedFunctionSymbol(callableId)
}
return listOf(function.symbol)
}
private fun buildClass(classId: ClassId): FirRegularClass {
return buildRegularClass {
moduleData = session.moduleData
origin = key.origin
classKind = ClassKind.CLASS
scopeProvider = session.kotlinScopeProvider
status = FirResolvedDeclarationStatusImpl(Visibilities.Public, Modality.FINAL, EffectiveVisibility.Public)
name = classId.shortClassName
symbol = FirRegularClassSymbol(classId)
superTypeRefs += session.builtinTypes.anyType
}
}
override fun getCallableNamesForGeneratedClass(classSymbol: FirClassSymbol<*>): Set<Name> {
return if (classSymbol.classId in classIdsForMatchedClasses) {
setOf(MATERIALIZE_NAME)
} else {
emptySet()
}
}
override fun getNestedClassifiersNamesForGeneratedClass(classSymbol: FirClassSymbol<*>): Set<Name> {
return if (classSymbol.classId == GENERATED_CLASS_ID) {
return classIdsForMatchedClasses.keys.mapTo(mutableSetOf()) { it.shortClassName }
} else {
emptySet()
}
}
override fun hasPackage(packageFqName: FqName): Boolean {
return packageFqName == FOO_PACKAGE
}
override val key: FirPluginKey
get() = Key
override val predicate: DeclarationPredicate
get() = has("B".fqn())
}
private object MatchedClassAttributeKey : FirDeclarationDataKey()
private var FirRegularClass.matchedClass: ClassId? by FirDeclarationDataRegistry.data(MatchedClassAttributeKey)
@@ -1,27 +0,0 @@
FILE: functionForProperty.kt
@R|org/jetbrains/kotlin/fir/plugin/WithGenerated|() public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
@R|org/jetbrains/kotlin/fir/plugin/WithHello|() public final val x: R|kotlin/Int| = Int(1)
public get(): R|kotlin/Int|
public final fun helloX(): R|kotlin/Int|
}
public final class B : R|kotlin/Any| {
public constructor(): R|B| {
super<R|kotlin/Any|>()
}
@R|org/jetbrains/kotlin/fir/plugin/WithHello|() public final val x: R|kotlin/Int| = Int(1)
public get(): R|kotlin/Int|
}
public final fun test_1(a: R|A|): R|kotlin/Unit| {
R|<local>/a|.<Unresolved name: helloX>#()
}
public final fun test_2(b: R|B|): R|kotlin/Unit| {
R|<local>/b|.<Unresolved name: helloX>#()
}
@@ -1,22 +0,0 @@
import org.jetbrains.kotlin.fir.plugin.WithHello
import org.jetbrains.kotlin.fir.plugin.WithGenerated
import org.jetbrains.kotlin.fir.plugin.AllOpen
@WithGenerated
class A {
@WithHello
val x: Int = 1
}
class B {
@WithHello
val x: Int = 1
}
fun test_1(a: A) {
a.helloX() // should be OK
}
fun test_2(b: B) {
b.<!UNRESOLVED_REFERENCE!>helloX<!>() // should be an error
}
@@ -0,0 +1,28 @@
FILE: generatedClassWithMembersAndNestedClasses.kt
@R|org/jetbrains/kotlin/fir/plugin/B|() public final class Foo : R|kotlin/Any| {
public constructor(): R|bar/Foo| {
super<R|kotlin/Any|>()
}
public final fun foo(): R|kotlin/Unit| {
}
}
@R|org/jetbrains/kotlin/fir/plugin/B|() public final class Bar : R|kotlin/Any| {
public constructor(): R|bar/Bar| {
super<R|kotlin/Any|>()
}
public final fun bar(): R|kotlin/Unit| {
}
}
public final fun testConstructor(): R|kotlin/Unit| {
lval generatedClass: R|foo/AllOpenGenerated| = R|foo/AllOpenGenerated|()
}
public final fun testNestedClasses(): R|kotlin/Unit| {
lval nestedFoo: R|foo/AllOpenGenerated.NestedFoo| = Q|foo/AllOpenGenerated|.R|foo/AllOpenGenerated.NestedFoo|()
R|<local>/nestedFoo|.R|foo/AllOpenGenerated.NestedFoo.materialize|().R|bar/Foo.foo|()
lval nestedBar: R|foo/AllOpenGenerated.NestedBar| = Q|foo/AllOpenGenerated|.R|foo/AllOpenGenerated.NestedBar|()
R|<local>/nestedBar|.R|foo/AllOpenGenerated.NestedBar.materialize|().R|bar/Bar.bar|()
}
@@ -0,0 +1,27 @@
package bar
import foo.AllOpenGenerated
import org.jetbrains.kotlin.fir.plugin.B
@B
class Foo {
fun foo() {}
}
@B
class Bar {
fun bar() {}
}
fun testConstructor() {
val generatedClass: AllOpenGenerated = AllOpenGenerated()
}
fun testNestedClasses() {
val nestedFoo = AllOpenGenerated.NestedFoo()
nestedFoo.materialize().foo()
val nestedBar = AllOpenGenerated.NestedBar()
nestedBar.materialize().bar()
}
@@ -1,37 +0,0 @@
FILE: nestedClass.kt
public final fun <T> R|T|.also(block: R|(T) -> kotlin/Unit|): R|T| {
^also this@R|/also|
}
@R|org/jetbrains/kotlin/fir/plugin/WithNestedFoo|() public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
private final fun test(): R|A.Foo| {
^test <Resolution to classifier>#().<None of the following candidates is applicable because of receiver type mismatch: [/also]>#<R|A.Foo|>(<L> = also@fun <anonymous>(it: R|A.Foo|): R|kotlin/Unit| <inline=Unknown> {
R|<local>/it|.<Unresolved name: hello>#()
}
)
}
private final inner class Foo {
public constructor(): R|A.Foo|
public final fun hello(): R|kotlin/Int|
}
}
public final class B : R|kotlin/Any| {
public constructor(): R|B| {
super<R|kotlin/Any|>()
}
private final fun test(): <ERROR TYPE REF: Symbol not found for Foo> {
^test <Unresolved name: Foo>#().<None of the following candidates is applicable because of receiver type mismatch: [/also]>#<R|ERROR CLASS: Cannot infer argument for type parameter T|>(<L> = also@fun <anonymous>(it: <ERROR TYPE REF: Cannot infer argument for type parameter T>): R|kotlin/Unit| <inline=Unknown> {
R|<local>/it|.<Unresolved name: hello>#()
}
)
}
}
@@ -1,20 +0,0 @@
import org.jetbrains.kotlin.fir.plugin.WithNestedFoo
fun <T> T.also(block: (T) -> Unit): T = this
@WithNestedFoo
class A {
private fun test(): Foo {
return Foo().also {
it.hello() // should be OK
}
}
}
class B {
private fun test(): Foo {
return <!UNRESOLVED_REFERENCE!>Foo<!>().<!INAPPLICABLE_CANDIDATE!>also<!> {
<!UNRESOLVED_REFERENCE!>it<!>.<!UNRESOLVED_REFERENCE!>hello<!>() // should be an error
}
}
}
@@ -1,19 +0,0 @@
FILE: recursiveNestedClasses.kt
@R|org/jetbrains/kotlin/fir/plugin/B|() public final class SomeClass : R|kotlin/Any| {
public constructor(): R|SomeClass| {
super<R|kotlin/Any|>()
}
@R|org/jetbrains/kotlin/fir/plugin/B|() public final class Nested {
@R|org/jetbrains/kotlin/fir/plugin/B|() public final class Nested {
}
}
}
public final fun test_1(x: R|SomeClass.Nested|): R|kotlin/Unit| {
}
public final fun test_2(x: R|SomeClass.Nested.Nested|): R|kotlin/Unit| {
}
public final fun test_3(x: <ERROR TYPE REF: Symbol not found for SomeClass.Nested.Nested.Nested>): R|kotlin/Unit| {
}
@@ -1,8 +0,0 @@
import org.jetbrains.kotlin.fir.plugin.B
@B
class SomeClass
fun test_1(x: SomeClass.Nested) {}
fun test_2(x: SomeClass.Nested.Nested) {}
fun test_3(x: SomeClass.Nested.Nested.Nested) {}
@@ -50,21 +50,9 @@ public class FirAllOpenDiagnosticTestGenerated extends AbstractFirAllOpenDiagnos
}
@Test
@TestMetadata("functionForProperty.kt")
public void testFunctionForProperty() throws Exception {
runTest("plugins/fir/fir-plugin-prototype/testData/memberGen/functionForProperty.kt");
}
@Test
@TestMetadata("nestedClass.kt")
public void testNestedClass() throws Exception {
runTest("plugins/fir/fir-plugin-prototype/testData/memberGen/nestedClass.kt");
}
@Test
@TestMetadata("recursiveNestedClasses.kt")
public void testRecursiveNestedClasses() throws Exception {
runTest("plugins/fir/fir-plugin-prototype/testData/memberGen/recursiveNestedClasses.kt");
@TestMetadata("generatedClassWithMembersAndNestedClasses.kt")
public void testGeneratedClassWithMembersAndNestedClasses() throws Exception {
runTest("plugins/fir/fir-plugin-prototype/testData/memberGen/generatedClassWithMembersAndNestedClasses.kt");
}
@Test