New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle more corner cases for inherited accessors #2532
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,29 +10,93 @@ import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName | |
|
||
internal data class DescriptorFunctionsHolder( | ||
val regularFunctions: List<FunctionDescriptor>, | ||
val accessors: Map<PropertyDescriptor, List<FunctionDescriptor>> | ||
val accessors: Map<PropertyDescriptor, DescriptorAccessorHolder> | ||
) | ||
|
||
internal fun splitFunctionsAndAccessors( | ||
internal data class DescriptorAccessorHolder( | ||
val getter: FunctionDescriptor? = null, | ||
val setter: FunctionDescriptor? = null | ||
) | ||
|
||
/** | ||
* Separate regular Kotlin/Java functions and inherited Java accessors | ||
* to properly display properties inherited from Java. | ||
* | ||
* Take this example: | ||
* ``` | ||
* // java | ||
* public class JavaClass { | ||
* private int a = 1; | ||
* public int getA() { return a; } | ||
* public void setA(int a) { this.a = a; } | ||
* } | ||
* | ||
* // kotlin | ||
* class Bar : JavaClass() { | ||
* fun foo() {} | ||
* } | ||
* ``` | ||
* | ||
* It should result in: | ||
* - 1 regular function `foo` | ||
* - Map a=[`getA`, `setA`] | ||
*/ | ||
internal fun splitFunctionsAndInheritedAccessors( | ||
properties: List<PropertyDescriptor>, | ||
functions: List<FunctionDescriptor> | ||
): DescriptorFunctionsHolder { | ||
val (javaMethods, kotlinFunctions) = functions.partition { it is JavaMethodDescriptor } | ||
if (javaMethods.isEmpty()) { | ||
return DescriptorFunctionsHolder(regularFunctions = kotlinFunctions, emptyMap()) | ||
} | ||
|
||
val propertiesByName = properties.associateBy { it.name.asString() } | ||
val regularFunctions = mutableListOf<FunctionDescriptor>() | ||
val accessors = mutableMapOf<PropertyDescriptor, MutableList<FunctionDescriptor>>() | ||
functions.forEach { function -> | ||
val regularFunctions = ArrayList<FunctionDescriptor>(kotlinFunctions) | ||
|
||
val accessors = mutableMapOf<PropertyDescriptor, DescriptorAccessorHolder>() | ||
javaMethods.forEach { function -> | ||
val possiblePropertyNamesForFunction = function.toPossiblePropertyNames() | ||
val property = possiblePropertyNamesForFunction.firstNotNullOfOrNull { propertiesByName[it] } | ||
if (property != null && function.isAccessorFor(property)) { | ||
accessors.getOrPut(property, ::mutableListOf).add(function) | ||
accessors.compute(property) { prop, accessorHolder -> | ||
if (function.isGetterFor(prop)) | ||
accessorHolder?.copy(getter = function) ?: DescriptorAccessorHolder(getter = function) | ||
else | ||
accessorHolder?.copy(setter = function) ?: DescriptorAccessorHolder(setter = function) | ||
} | ||
} else { | ||
regularFunctions.add(function) | ||
} | ||
} | ||
|
||
val accessorLookalikes = removeNonAccessorsReturning(accessors) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make a filter here. It is more obvious There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, one simple filter would not do :( We need to both remove elements from Filter would only return the elements which need to be added to This could be done in multiple steps, something like this, but IMHO it involves many excessive operations which can be substituted with a single iteration val nonAccessibleProperties: Map<PropertyDescriptor, DescriptorAccessorHolder> =
accessors.filterValues { it.getter == null && it.setter != null }
val functionsOfNonAccessibleProperties = nonAccessibleProperties.values.map { it.setter!! }
regularFunctions.addAll(functionsOfNonAccessibleProperties)
nonAccessibleProperties.keys.forEach { accessors.remove(it) } |
||
regularFunctions.addAll(accessorLookalikes) | ||
|
||
return DescriptorFunctionsHolder(regularFunctions, accessors) | ||
} | ||
|
||
internal fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { | ||
/** | ||
* If a field has no getter, it's not accessible as a property from Kotlin's perspective, | ||
* but it still might have a setter lookalike. In this case, this "setter" should be just a regular function | ||
* | ||
* @return removed elements | ||
*/ | ||
private fun removeNonAccessorsReturning( | ||
propertyAccessors: MutableMap<PropertyDescriptor, DescriptorAccessorHolder> | ||
): List<FunctionDescriptor> { | ||
val nonAccessors = mutableListOf<FunctionDescriptor>() | ||
propertyAccessors.entries.removeIf { (_, accessors) -> | ||
if (accessors.getter == null && accessors.setter != null) { | ||
nonAccessors.add(accessors.setter) | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
return nonAccessors | ||
} | ||
|
||
private fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { | ||
val stringName = this.name.asString() | ||
return when { | ||
JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() } | ||
|
@@ -41,7 +105,7 @@ internal fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { | |
} | ||
} | ||
|
||
internal fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> { | ||
private fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> { | ||
val stringName = functionDescriptor.name.asString() | ||
// In java, the convention for boolean property accessors is as follows: | ||
// - `private boolean active;` | ||
|
@@ -63,21 +127,19 @@ internal fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): L | |
return listOfNotNull(javaPropName, kotlinPropName) | ||
} | ||
|
||
internal fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean { | ||
return this.isGetterFor(property) || this.isSetterFor(property) | ||
private fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean { | ||
return (this.isGetterFor(property) || this.isSetterFor(property)) | ||
&& !property.visibility.isPublicAPI | ||
&& this.visibility.isPublicAPI | ||
} | ||
|
||
internal fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean { | ||
private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean { | ||
return this.returnType == property.returnType | ||
&& this.valueParameters.isEmpty() | ||
&& !property.visibility.isPublicAPI | ||
&& this.visibility.isPublicAPI | ||
} | ||
|
||
internal fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean { | ||
private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean { | ||
return this.valueParameters.size == 1 | ||
&& this.valueParameters[0].type == property.returnType | ||
&& !property.visibility.isPublicAPI | ||
&& this.visibility.isPublicAPI | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there reasons to use
ArrayList
instead ofList
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, ArrayList's constructor takes another collection as an argument and adds all of its elements into the newly created list. It's more efficient as by default even
addAll
still adds elements one by one, causing resizes and so on