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 ImmutablesInterfaceDefaultValue #1761

Closed
wants to merge 3 commits into from
Closed

Conversation

schlosna
Copy link
Contributor

@schlosna schlosna commented May 11, 2021

Before this PR

In code reviews, noticed some uses of @Value.Immutable on default methods in interfaces without corresponding @Value.Default, @Value.Lazy or @Value.Derived

After this PR

==COMMIT_MSG==
Add ImmutablesInterfaceDefaultValue

@Value.Immutable interface default methods should be annotated @Value.Default
==COMMIT_MSG==

Possible downsides?

Refactoring suggestion assumes @Value.Default was intended if not already annotated @Value.Lazy or @Value.Derived

schlosna added 2 commits May 11, 2021 11:20
@Value.Immutable interface default methods should be annotated @Value.Default
@changelog-app
Copy link

changelog-app bot commented May 11, 2021

Generate changelog in changelog/@unreleased

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

Add ImmutablesInterfaceDefaultValue

@Value.Immutable interface default methods should be annotated @Value.Default

Check the box to generate changelog(s)

  • Generate changelog entry

@policy-bot policy-bot bot requested a review from jkozlowski May 11, 2021 15:45
jkozlowski
jkozlowski previously approved these changes May 12, 2021
Copy link
Contributor

@jkozlowski jkozlowski left a comment

Choose a reason for hiding this comment

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

LGTM! Just a comment, but don't think it's blocking

@CRogers / @tpetracca for SA, I think you were both mentioning adding some more rules for immutables usage.

&& ASTHelpers.hasAnnotation(enclosingClass, "org.immutables.value.Value.Immutable", state)) {
MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree);
if (methodSymbol != null
&& methodSymbol.isDefault()
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this would read slightly better as a series of matchers?

private static final Matcher<MethodTree> methodReturnsAutoCloseable = Matchers.allOf(

But unclear what is more idiomatic.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think if would look like this:

private static final Matcher<Tree> MATCHER = Matchers.allOf(
            Matchers.enclosingClass(Matchers.hasAnnotation("org.immutables.value.Value.Immutable")),
            Matchers.hasModifier(Modifier.DEFAULT),
            Matchers.not(Matchers.anyOf(
                    Matchers.hasAnnotation("org.immutables.value.Value.Default"),
                    Matchers.hasAnnotation("org.immutables.value.Value.Derived"),
                    Matchers.hasAnnotation("org.immutables.value.Value.Lazy"))));

Meaning you can collapse the if-statement to just:

if (MATCHER.matches(tree, state)) {
  ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, will pull these matchers out

linkType = LinkType.CUSTOM,
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks",
severity = BugPattern.SeverityLevel.ERROR,
summary = "@Value.Immutable interface default methods should be annotated @Value.Default")
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 detect if an immutables style annotation is used with defaultAsDefault = true? Example: https://immutables.github.io/style.html#custom-immutable-annotation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, haven't test that yet.

I've seen varying .*Immutable(s)?Style annotations used across projects, and might be useful to encourage consistent usage.

"",
" // BUG: Diagnostic contains: @Value.Immutable interface"
+ " default methods should be annotated with"
+ " @Value.Default, @Value.Derived, or @Value.Lazy",
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this should be an error? Default methods are regularly used as helper instance methods rather than anything an @Value.Default, and this is a totally valid use of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I guess you need to check for no args?

Copy link
Contributor

Choose a reason for hiding this comment

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

Even no args is a perfectly valid use of the default methods in @Value.Immutable interfaces, to use as helper methods. I'm trying to an example externally, this is used a lot internally.

Copy link
Contributor

@jkozlowski jkozlowski May 12, 2021

Choose a reason for hiding this comment

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

:sigh:, I guess we can't really do anything here then, because this will suddenly start failing everyone? I think annotating with one of the annotations is not a change? And then I thought we have some automation for baseline bumps to upgrade people's code if we can.

So I guess question is are there cases where you'd legitimately not want any annotations, and adding an annotation would break the world?

Copy link
Contributor

Choose a reason for hiding this comment

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

Adding an annotation is 100% a breaking change (and in all currently working cases not what you want). If you serialise the immutable, suddenly you end up saving more data. It adds new methods to the builder which you may not want.

We can't tell whether someone forgot to add a @Value.Default or they're using a default method as a helper function that should not be persisted/in the builder - that's up the dev to decide. If they have forgotten to add a @Value.Default then it should be obvious elsewhere when they can't use it in a builder.

I don't think this check is possible to implement statically.

Copy link
Contributor

Choose a reason for hiding this comment

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

for the 4 examples you gave, are they all not validly annotated as @Value.Derived? or is the whole idea someone might directly set them as well in the builder.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can't tell whether someone forgot to add a @Value.Default or they're using a default method as a helper function that should not be persisted/in the builder - that's up the dev to decide. If they have forgotten to add a @Value.Default then it should be obvious elsewhere when they can't use it in a builder.

This is true except for the most important case, of interfaces declaring configuration options. A forgotten Value.Default will only be exposed there once someone tries to override a default config and the override will not take - which can be difficult to notice in the first place.

I think this check applied only to things referenced in the service configuration classes/interfaces would be a great benefit

Copy link
Member

Choose a reason for hiding this comment

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

I don't see why configuration definitions are any different. We regularly use default methods in configuration definitions for values that are trivially derived.

@jkozlowski jkozlowski dismissed their stale review May 12, 2021 15:10

Sounds like we have a problem

@policy-bot policy-bot bot requested a review from jkozlowski May 12, 2021 15:10
@pkoenig10
Copy link
Member

pkoenig10 commented May 17, 2021

I'm not sure I agree with this check. We have a number of legitimate uses of default methods that do not need to be memoized because they simply return constants or perform a trivial calculation (like a lookup in a map field). I don't want to bloat the size of my immutables objects with these fields that are practically free to compute on-demand. Some of these objects are used in caches where memory is at a premium and we'd like to avoid increasing the size of our objects unnecessarily.

For an example, see AtlasUser in our internal authentication service.

@schlosna
Copy link
Contributor Author

Going to close this out for now given discussion

@schlosna schlosna closed this May 17, 2021
@schlosna schlosna deleted the ds/default-immutables branch June 10, 2021 21:42
@rhuffy rhuffy restored the ds/default-immutables branch November 27, 2024 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants