Skip to content
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

Replace Kotlin getters/setters with property access in codegen #496 #1002

Merged
merged 9 commits into from
Sep 27, 2022

Conversation

volivan239
Copy link
Collaborator

@volivan239 volivan239 commented Sep 23, 2022

Description

Now codegen properly treats Kotlin's autogenerated getters/setters and uses direct property access instead of them.

Kotlin properties are treated as fields. However, instead of FieldId.isAccessibleFrom which is now private, methods FieldId.canBeReadFrom and FieldId.canBeSetFrom should be used. First one behaves for Java code in the same way as isAccessibleFrom did before, but for Kotlin it also returns true if there is an accessible getter. Behavior of FieldId.canBeSetFrom hasn't been changed for Java, but for Kotlin it now also returns true if there is accessible setter/getter pair. See docs for more details.

These two methods guarantee that codegen produces code with field access where it is possible. However, some calls to getters/setters may be produced by AssembleModelGenerator, so this PR also adds replaceCgExecutableCallWithFieldAccessIfNeeded to replace such calls with direct accesses.

Fixes #496

Type of Change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Manual Scenario

Works correctly on examples from #496 and #948

Checklist (remove irrelevant options):

This is the author self-check list

  • The change followed the style guidelines of the UTBot project
  • Self-review of the code is passed
  • The change contains enough commentaries, particularly in hard-to-understand areas
  • New documentation is provided or existed one is altered
  • No new warnings
  • New tests have been added
  • All tests pass locally with my changes

@volivan239 volivan239 marked this pull request as ready for review September 23, 2022 13:38
@volivan239 volivan239 force-pushed the volivan239/fix_kotlin_property_access_rendering branch 2 times, most recently from b9fb58a to 31ddce3 Compare September 24, 2022 16:58
+createCgExecutableCallFromUtExecutableCall(statementModel)
val call = createCgExecutableCallFromUtExecutableCall(statementModel)
val callOrAccess: CgStatement = replaceCgExecutableCallWithFieldAccessIfNeeded(call)
if (callOrAccess is CgExecutableCall)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve this code fragment to make it more readable, please

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be clearer now. Also, changed return type of replaceCgExecutableCallWithFieldAccessIfNeeded to be nullable and added docs to it

@@ -236,6 +246,7 @@ internal class CgVariableConstructor(val context: CgContext) :
val initExpr = if (isPrimitiveWrapperOrString(type)) {
cgLiteralForWrapper(params)
} else {
// TODO: if instantiation chain could be a setter call, we need to replace it in Kotlin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an instantiation call, now instantiation chain right now. Anyway, I can't imagine a situation when the top-level instantiation call is a setter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for advice, deleted the question

@@ -261,6 +272,23 @@ internal class CgVariableConstructor(val context: CgContext) :
return cgCall
}

private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement {
if (call !is CgMethodCall || context.codegenLanguage != CodegenLanguage.KOTLIN)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it seems better to write code-specific logic in a way like

when (context.codegenlangauge) {
is Java -> return call
is Kotlin -> do some specific logic
}

But I do not insist on it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here, didn't check for other places

val caller = call.caller ?: return call

caller.type.fieldThisIsSetterFor(call.executableId)?.let {
return CgAssignment(caller[it], call.arguments.single())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you require that arguments contains single item here as in line 285?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do -- at call.arguments.single()....

return CgAssignment(caller[it], call.arguments.single())
}
caller.type.fieldThisIsGetterFor(call.executableId)?.let {
require(call.arguments.isEmpty())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require is more useful with a message with failure description

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added message

*/
internal fun ClassId.fieldThisIsGetterFor(methodId: MethodId): FieldId? =
allDeclaredFieldIds.firstOrNull { !it.isStatic && it.getter == methodId }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be singleOrNull? Same for the second method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

* Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter)
*/
internal fun ClassId.fieldThisIsSetterFor(methodId: MethodId): FieldId? =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest the name fieldThatIsSetWith.
Current name means that field is a setter isself, it seems strange.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this and the method below

@@ -343,7 +343,7 @@ private fun createInstance(visibility: Visibility, language: CodegenLanguage): S
}
CodegenLanguage.KOTLIN -> {
"""
${visibility by language}fun createInstance(className: String): kotlin.Any? {
${visibility by language}fun createInstance(className: String): kotlin.Any {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't understand this change, but can believe that is is possible :)

Copy link
Collaborator Author

@volivan239 volivan239 Sep 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, this method can't return null (at least, no compilation error is produced after this change), this may help to avoid unnecessary ?.-calls

@volivan239 volivan239 force-pushed the volivan239/fix_kotlin_property_access_rendering branch from e01920b to 38fb0d0 Compare September 27, 2022 15:21
@volivan239 volivan239 merged commit bcece73 into main Sep 27, 2022
@volivan239 volivan239 deleted the volivan239/fix_kotlin_property_access_rendering branch September 27, 2022 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compilation errors in code with setters generated on Kotlin
2 participants