Skip to content

Fall back to canonical constructor in constructor resolution when using Java Records #2694

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

Closed
wants to merge 2 commits into from

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented Oct 4, 2022

We now fall back to the canonical constructor of records when we cannot disambiguate which constructor to use.

Also, reflect the Kotlin persistence creator mechanism.

Closes #2625

…ng Java Records.

We now fall back to the canonical constructor of records when we cannot disambiguate which constructor to use.

Also, reflect the Kotlin persistence creator mechanism.

Closes #2625
@mp911de mp911de added the type: enhancement A general enhancement label Oct 4, 2022
@mp911de mp911de requested review from odrotbohm and schauder October 4, 2022 09:25
Copy link
Member

@odrotbohm odrotbohm left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -124,12 +125,45 @@ <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInf
}
}

if (rawOwningType.isRecord() && (candidates.size() > 1 || (noArg != null && !candidates.isEmpty()))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I find the combination of || and && rather confusing, especially in combination with the negations.

Isn't this equivalent to rawOwningType.isRecord() && (candidates.size != 1 || noArg == null)?
Either way, I think we should simplify this expression and either add a comment or extract to a method to explain the reasoning behind this condition.

Copy link
Contributor

Choose a reason for hiding this comment

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

After thinking about it again: no my expression is wrong, which kind of proves the point that I consider the current version confusing.

I think in general the checks would be simpler if we would add the noargs constructor to the candidates as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking even more about it, wouldn't it make more sense to handle the cases in the following order (with noargs added to the candidates):

  • no candidate
  • single candidate
  • records

Also, the whole logic should really fail when there is more than one PreferredCreator annotation, shouldn't it? It currently it seems to just take the first.

Copy link
Member Author

Choose a reason for hiding this comment

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

Records resolve the no-candidate case by falling back to the constructor that is always there, we do the same for Kotlin data classes already so it makes sense to align. Selecting the no-args constructor for records is pointless with the idea to mimic with… style with our InstantiationAwarePropertyAccessor.

@Test // GH-2332
void detectsAnnotatedRecordConstructor() {

assertThat(PreferredConstructorDiscoverer.discover(RecordWithPersistenceCreator.class)).satisfies(ctor -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make these either soft asserts, or separate tests?

Copy link
Member Author

Choose a reason for hiding this comment

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

For whatever reason, the entire test class is written in that way. While I find that style strange (likely that comes from our Optional experiments), we could apply some polishing post-RC1.

@@ -149,6 +150,34 @@ void detectsMetaAnnotatedValueAnnotation() {
});
}

@Test // GH-2332
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see a test for the behavior of a private all args constructor for Records, because right now I can't easily tell how the code behaves.

Copy link
Member Author

Choose a reason for hiding this comment

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

Constructors that match the canonical constructor cannot be private. Also, a constructor matching the canonical makes Java backoff from its default constructor creation (same as with public no-arg constructors for regular classes).

@mp911de mp911de closed this Oct 4, 2022
mp911de added a commit that referenced this pull request Oct 4, 2022
…ng Java Records.

We now fall back to the canonical constructor of records when we cannot disambiguate which constructor to use.

Also, reflect the Kotlin persistence creator mechanism.

Closes #2625
Original pull request: #2694.
@mp911de mp911de deleted the issue/2625 branch October 4, 2022 13:05
@mp911de mp911de added this to the 3.0 RC1 (2022.0.0) milestone Oct 4, 2022
gregturn pushed a commit that referenced this pull request Apr 24, 2023
Leverage things like Java var keyword to simplify test cases.

Resolves #2695.
Related: #2694.
Original pull request: #2724.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fall back to canonical constructor in constructor resolution when using Java Records
3 participants