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

Add support for Kotlin inline classes [DATACMNS-1517] #1947

Open
spring-projects-issues opened this issue Apr 17, 2019 · 11 comments
Open

Add support for Kotlin inline classes [DATACMNS-1517] #1947

spring-projects-issues opened this issue Apr 17, 2019 · 11 comments
Assignees

Comments

@spring-projects-issues
Copy link

@spring-projects-issues spring-projects-issues commented Apr 17, 2019

Wyatt Smith opened DATACMNS-1517 and commented

Here is an example with the bug: https://github.com/wyattjsmith1/SpringDataBug

When this runs, there is an IndexOutOfBoundsException. This is caused by kotlin's synthetic constructor. I believe the issues is that synthetic constructors aren't filtered before.

buildPreferredConstructor at org.springframework.data.mapping.model.PreferredConstructorDiscoverer.Discoverers#discover, but there is probably a better solution to this.

Changing AccountId in the data class to a String causes the application to work properly. Admittedly, inline classes are still experimental for Kotlin, but this might be worth investigating


Issue Links:

  • DATAGRAPH-1330 Properties with type of Kotlin inline class has mangled name

7 votes, 10 watchers

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Apr 18, 2019

Mark Paluch commented

I switched this ticket from a bug report to a new feature because inline classes weren't there when we built support for Kotlin. Let's see whether and how we can support this type of classes

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Oct 10, 2019

Ben Madore commented

Just as another data point, ran into this same issue today, (should have searched here first instead of google!). Would love if this worked

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Jan 30, 2020

Mark Paluch commented

Classes, that use Kotlin inline classes in a constructor receive a synthetic public constructor while the actual constructor is made private. We've seen a similar behavior with data classes but there the synthetic constructor is accepting defaulting masks. Our code currently can find and use synthetic constructors but only if they also use a defaulting mask as in data classes.

The newly introduced Kotlin behavior has two effects:

  1. Spring's ParameterNameDiscoverer returns a name array that does not contain a name for the synthetic argument (e.g. constructor takes 4 arguments [real 3 arguments + synthetic constructor marker] but the name array contains only 3 name items). Therefore we see an IndexOutOfBoundException during the class introspection.
  2. KotlinClassGeneratingEntityInstantiator expects synthetic constructors that contain a DefaultConstructorMarker also accept one or more defaulting mask arguments. Types that accept a inline class argument don't contain defaulting masks.

Basically, if inline classes would not mess with the public constructor, things would work as expected without further do. After investigating a bit on this topic, support of types accepting inline classes requires a bigger effort to make it work

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Jan 30, 2020

Ben Madore commented

I wonder if it's worth opening up a ticket against kotlin to provide this as feedback on their Inline Class implementation which is still "experimental" and thus should be possible to change? I can open up a ticket there referencing this - though i suppose the Spring team might have more direct channels of communication there?

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Feb 3, 2020

Ben Madore commented

Opened https://youtrack.jetbrains.com/issue/KT-36320 with a link back to this issue

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented Mar 12, 2020

gaerfield commented

Possible intermediate workaround: https://stackoverflow.com/a/60652012/5029667

@matthewadams
Copy link

@matthewadams matthewadams commented Jun 18, 2021

NB: I'm not using inline classes, just data classes here.

I'm seeing something similar to this on a brand new, greenfield project using Kotlin 1.5.10, Spring Boot 2.5.1, Spring Data 2021.0.1 (w/Spring Data Commons 2.5.1 & Spring Data MongoDB 3.2.1). I'm using immutable data classes for my @Documents and their value(s). When I attempt to read a Schedule from mongo, I get the exception org.springframework.data.mapping.MappingException: Parameter org.springframework.data.mapping.PreferredConstructor$Parameter@80a6e44c does not have a name!. The parameter in question is, indeed, the DefaultConstructorMarker.

The offending data class is :

sealed interface Availability {
    companion object {
        val CONVENTIONAL_8TO5_WITH_LUNCH_RECURRENCE: CronString = "0/30 8-12,13-17 * * MON-FRI"
        val CONVENTIONAL_8TO5_RECURRENCE: CronString = "0/30 8-17 * * MON-FRI"
        val CONVENTIONAL_9TO5_WITH_LUNCH_RECURRENCE: CronString = "0/30 9-12,13-17 * * MON-FRI"
        val CONVENTIONAL_9TO5_RECURRENCE: CronString = "0/30 9-17 * * MON-FRI"
        val DEFAULT_RECURRENCE = CONVENTIONAL_9TO5_WITH_LUNCH_RECURRENCE
        val DEFAULT_SLOT_MINUTES = 30U.toUByte()
        val DEFAULT_ALLOWED_BOOKING_COUNT = 1U.toUByte()
    }
    val handle: String
    val slotMinutes: UByte
    val allowedBookingCount: UByte
    val name: String?
    val effectivity: Interval?
}
data class RecurringAvailability(
    val recurrence: CronString = DEFAULT_RECURRENCE,
    override val handle: String = UUID.randomUUID().toString(),
    override val slotMinutes: UByte = DEFAULT_SLOT_MINUTES,
    override val allowedBookingCount: UByte = DEFAULT_ALLOWED_BOOKING_COUNT,
    override val effectivity: Interval? = null,
    override val name: String? = null,
) : Availability

(fyi, CronString is typealias CronString = String)

Any known workarounds here?

@matthewadams
Copy link

@matthewadams matthewadams commented Jun 19, 2021

Answered my own question, thanks to #2215 (comment). I was using unsigned types. Changing them to Int made the issue go away.

@mp911de
Copy link
Member

@mp911de mp911de commented Jun 21, 2021

A few things come together with inline classes: Synthetic constructors that do not follow any known scheme and property accessors with rewritten names. I think one could provide bean introspectors/bean descriptor factories to address specifics of how Kotlin classes using inline/unsigned are compiled by Kotlin. For the constructor issue, it's difficult to find a reasonable approach.

@udalov
Copy link

@udalov udalov commented Jul 12, 2021

@mp911de Sorry for the delay. Inline class constructors are not supposed to be invoked directly, and that's by design. There's a public static method constructor-impl however, which is used for example by the Kotlin compiler when inline class is created. Maybe you can invoke it too?

@mp911de
Copy link
Member

@mp911de mp911de commented Jul 13, 2021

The issue is that the entire instantiation mechanism in Spring Data relies solely on constructors. We discover a persistence constructor, invoke it via reflection and have code to generate bytecode to call constructors via bytecode. Switching to factory methods requires a significant rewrite and, what's more important, we need to adopt a different concept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants