Skip to content

Commit a4c53b6

Browse files
authored
Merge pull request #357 from themkat/implement_external_abstract_member
Implement abstract function - Extend to support external methods (Java standard library etc.)
2 parents 322fb60 + ce9311f commit a4c53b6

File tree

4 files changed

+295
-60
lines changed

4 files changed

+295
-60
lines changed

server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,29 @@ import org.javacs.kt.index.SymbolIndex
77
import org.javacs.kt.position.offset
88
import org.javacs.kt.position.position
99
import org.javacs.kt.util.toPath
10+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
11+
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
12+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
13+
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
14+
import org.jetbrains.kotlin.descriptors.isInterface
15+
import org.jetbrains.kotlin.descriptors.Modality
1016
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
1117
import org.jetbrains.kotlin.lexer.KtTokens
1218
import org.jetbrains.kotlin.psi.KtClass
1319
import org.jetbrains.kotlin.psi.KtDeclaration
1420
import org.jetbrains.kotlin.psi.KtNamedFunction
21+
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
22+
import org.jetbrains.kotlin.psi.KtSuperTypeListEntry
23+
import org.jetbrains.kotlin.psi.KtTypeArgumentList
24+
import org.jetbrains.kotlin.psi.KtTypeReference
1525
import org.jetbrains.kotlin.psi.psiUtil.containingClass
1626
import org.jetbrains.kotlin.psi.psiUtil.endOffset
1727
import org.jetbrains.kotlin.psi.psiUtil.isAbstract
1828
import org.jetbrains.kotlin.psi.psiUtil.startOffset
1929
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
30+
import org.jetbrains.kotlin.types.KotlinType
31+
import org.jetbrains.kotlin.types.TypeProjection
32+
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
2033

2134
private const val DEFAULT_TAB_SIZE = 4
2235

@@ -27,7 +40,7 @@ class ImplementAbstractFunctionsQuickFix : QuickFix {
2740
val startCursor = offset(file.content, range.start)
2841
val endCursor = offset(file.content, range.end)
2942
val kotlinDiagnostics = file.compile.diagnostics
30-
43+
3144
// If the client side and the server side diagnostics contain a valid diagnostic for this range.
3245
if (diagnostic != null && anyDiagnosticMatch(kotlinDiagnostics, startCursor, endCursor)) {
3346
// Get the class with the missing functions
@@ -70,31 +83,47 @@ private fun getAbstractFunctionStubs(file: CompiledFile, kotlinClass: KtClass) =
7083
// For each of the super types used by this class
7184
kotlinClass.superTypeListEntries.mapNotNull {
7285
// Find the definition of this super type
73-
val descriptor = file.referenceAtPoint(it.startOffset)?.second
74-
val superClass = descriptor?.findPsi()
86+
val referenceAtPoint = file.referenceExpressionAtPoint(it.startOffset)
87+
val descriptor = referenceAtPoint?.second
88+
89+
val classDescriptor = getClassDescriptor(descriptor)
90+
7591
// If the super class is abstract or an interface
76-
if (superClass is KtClass && (superClass.isAbstract() || superClass.isInterface())) {
77-
// Get the abstract functions of this super type that are currently not implemented by this class
78-
val abstractFunctions = superClass.declarations.filter {
79-
declaration -> isAbstractFunction(declaration) && !overridesDeclaration(kotlinClass, declaration)
92+
if (null != classDescriptor && (classDescriptor.kind.isInterface || classDescriptor.modality == Modality.ABSTRACT)) {
93+
val superClassTypeArguments = getSuperClassTypeProjections(file, it)
94+
classDescriptor.getMemberScope(superClassTypeArguments).getContributedDescriptors().filter { classMember ->
95+
classMember is FunctionDescriptor && classMember.modality == Modality.ABSTRACT && !overridesDeclaration(kotlinClass, classMember)
96+
}.map { function ->
97+
createFunctionStub(function as FunctionDescriptor)
8098
}
81-
// Get stubs for each function
82-
abstractFunctions.map { function -> getFunctionStub(function as KtNamedFunction) }
8399
} else {
84100
null
85101
}
86102
}.flatten()
87103

88-
private fun isAbstractFunction(declaration: KtDeclaration): Boolean =
89-
declaration is KtNamedFunction && !declaration.hasBody()
90-
&& (declaration.containingClass()?.isInterface() ?: false || declaration.hasModifier(KtTokens.ABSTRACT_KEYWORD))
104+
// interfaces are ClassDescriptors by default. When calling AbstractClass super methods, we get a ClassConstructorDescriptor
105+
private fun getClassDescriptor(descriptor: DeclarationDescriptor?): ClassDescriptor? = if (descriptor is ClassDescriptor) {
106+
descriptor
107+
} else if (descriptor is ClassConstructorDescriptor) {
108+
descriptor.containingDeclaration
109+
} else {
110+
null
111+
}
112+
113+
private fun getSuperClassTypeProjections(file: CompiledFile, superType: KtSuperTypeListEntry): List<TypeProjection> = superType.typeReference?.typeElement?.children?.filter {
114+
it is KtTypeArgumentList
115+
}?.flatMap {
116+
(it as KtTypeArgumentList).arguments
117+
}?.mapNotNull {
118+
(file.referenceExpressionAtPoint(it?.startOffset ?: 0)?.second as? ClassDescriptor)?.defaultType?.asTypeProjection()
119+
} ?: emptyList()
91120

92121
// Checks if the class overrides the given declaration
93-
private fun overridesDeclaration(kotlinClass: KtClass, declaration: KtDeclaration): Boolean =
122+
private fun overridesDeclaration(kotlinClass: KtClass, descriptor: FunctionDescriptor): Boolean =
94123
kotlinClass.declarations.any {
95-
if (it.name == declaration.name && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
96-
if (it is KtNamedFunction && declaration is KtNamedFunction) {
97-
parametersMatch(it, declaration)
124+
if (it.name == descriptor.name.asString() && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
125+
if (it is KtNamedFunction) {
126+
parametersMatch(it, descriptor)
98127
} else {
99128
true
100129
}
@@ -104,19 +133,21 @@ private fun overridesDeclaration(kotlinClass: KtClass, declaration: KtDeclaratio
104133
}
105134

106135
// Checks if two functions have matching parameters
107-
private fun parametersMatch(function: KtNamedFunction, functionDeclaration: KtNamedFunction): Boolean {
108-
if (function.valueParameters.size == functionDeclaration.valueParameters.size) {
136+
private fun parametersMatch(function: KtNamedFunction, functionDescriptor: FunctionDescriptor): Boolean {
137+
if (function.valueParameters.size == functionDescriptor.valueParameters.size) {
109138
for (index in 0 until function.valueParameters.size) {
110-
if (function.valueParameters[index].name != functionDeclaration.valueParameters[index].name) {
139+
if (function.valueParameters[index].name != functionDescriptor.valueParameters[index].name.asString()) {
111140
return false
112-
} else if (function.valueParameters[index].typeReference?.name != functionDeclaration.valueParameters[index].typeReference?.name) {
141+
} else if (function.valueParameters[index].typeReference?.typeName() != functionDescriptor.valueParameters[index].type.unwrappedType().toString()) {
142+
// Note: Since we treat Java overrides as non nullable by default, the above test will fail when the user has made the type nullable.
143+
// TODO: look into this
113144
return false
114145
}
115146
}
116147

117-
if (function.typeParameters.size == functionDeclaration.typeParameters.size) {
148+
if (function.typeParameters.size == functionDescriptor.typeParameters.size) {
118149
for (index in 0 until function.typeParameters.size) {
119-
if (function.typeParameters[index].variance != functionDeclaration.typeParameters[index].variance) {
150+
if (function.typeParameters[index].variance != functionDescriptor.typeParameters[index].variance) {
120151
return false
121152
}
122153
}
@@ -128,8 +159,28 @@ private fun parametersMatch(function: KtNamedFunction, functionDeclaration: KtNa
128159
return false
129160
}
130161

131-
private fun getFunctionStub(function: KtNamedFunction): String =
132-
"override fun" + function.text.substringAfter("fun") + " { }"
162+
private fun KtTypeReference.typeName(): String? = this.name ?: this.typeElement?.children?.filter {
163+
it is KtSimpleNameExpression
164+
}?.map {
165+
(it as KtSimpleNameExpression).getReferencedName()
166+
}?.firstOrNull()
167+
168+
private fun createFunctionStub(function: FunctionDescriptor): String {
169+
val name = function.name
170+
val arguments = function.valueParameters.map { argument ->
171+
val argumentName = argument.name
172+
val argumentType = argument.type.unwrappedType()
173+
174+
"$argumentName: $argumentType"
175+
}.joinToString(", ")
176+
val returnType = function.returnType?.unwrappedType()?.toString()?.takeIf { "Unit" != it }
177+
178+
return "override fun $name($arguments)${returnType?.let { ": $it" } ?: ""} { }"
179+
}
180+
181+
// about types: regular Kotlin types are marked T or T?, but types from Java are (T..T?) because nullability cannot be decided.
182+
// Therefore we have to unpack in case we have the Java type. Fortunately, the Java types are not marked nullable, so we default to non nullable types. Let the user decide if they want nullable types instead. With this implementation Kotlin types also keeps their nullability
183+
private fun KotlinType.unwrappedType(): KotlinType = this.unwrap().makeNullableAsSpecified(this.isMarkedNullable)
133184

134185
private fun getDeclarationPadding(file: CompiledFile, kotlinClass: KtClass): String {
135186
// If the class is not empty, the amount of padding is the same as the one in the last declaration of the class

0 commit comments

Comments
 (0)