-
Notifications
You must be signed in to change notification settings - Fork 39
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
Introduce ImmutableCollection
templates
#40
Introduce ImmutableCollection
templates
#40
Conversation
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.
Could it make sense to also do List.of(...)
-> ImmutableList.of(...)
and similar for other collections? (e.g. Set
, Map
)
(Not sure if already exists or planned for another PR)
That sounds like a good addition! So as you mention, not only |
e8ce80b
to
8a3a724
Compare
8a3a724
to
0b6b9f4
Compare
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.
I was pretty sure that I was reviewing the last version of the code. Apparently, something went wrong and some comments are on an older version.. Sorry for that.
These templates will have a nice impact on the code 🚀 !
} | ||
|
||
@AfterTemplate | ||
ImmutableCollection<T> after(T item) { |
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.
In this case having ImmutableList
as return type would be more clear/honest about the rewrite we are doing here. Changing the return type to ImmutableList
would be consistent with the template above (ImmutableListOf
).
static final class ImmutableListOf2<T> { | ||
@BeforeTemplate | ||
List<T> before(T item, T item2) { | ||
return List.of(item, item2); |
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.
For the parameter names, we should have a consistent way of naming them. Instead of item
or i1
we could use e1, e2, etc.
as they also do in the actual implementation of the ImmutableList
.
Based on other templates in this repo (mainly in AssertJTemplates.java
), we can say that a sort of (unwritten) convention is that we "prefer" using the word element
.
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.
Ah, for the ImmutableSet
templates that are added here the e1
and e2
are used, so changing this would make it consistent 😄
@@ -186,4 +187,76 @@ private ImmutableListTemplates() {} | |||
return stream.collect(toImmutableSet()).asList(); | |||
} | |||
} | |||
|
|||
static final class ImmutableListOf<T> { |
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.
The other templates in this class have a Javadoc, let's add it for the other templates we introduce in this PR as well.
Perhaps we could do something like this example:
/**
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
The implementation of List.of(element ...)
is:
static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.ListN<>(e1, e2, e3);
}
So the example Javadoc has some similarities with the Refaster templates we are adding in this PR.
This also applies for the other introduced Refaster templates in Immutable{Map,Set}.java
.
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.
For most templates the Javadoc is now good. Only for this case, the emptyList()
I'm not 100% sure. So the List.of(..)
calls uses the ImmutableCollections
under the hood. However, for the emptyList()
this is not the case:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
I'm not sure what to write in the Javadoc there, so also leaving this open for other reviewers to give their input :)
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.
I think we should be very careful here (also when considering List.of()
as before template, see other comment). We have three different List
implementations with different behavior:
- Existing
@Before Collections.emptyList()
: uses Java'sCollections.EmptyList
which silently ignores calls tosort()
,replaceAll()
andremoveIf()
as there are no elements anyway. - Suggested
@Before List.of()
: uses Java'sImmutableCollections.ListN
which throwsUnsupportedOperationException
on those methods. - Existing
@After ImmutableList.of()
: uses Guava'sImmutableList
which throwsUnsupportedOperationException
on those methods.
My suggestion (after further implementation investigation):
List.of()
and ImmutableList.of()
have the most similar implementation and I would be happy to make that conversion. I would create an ErrorProne
check for Collections.emptyList()
to suggest using ImmutableList.of()
warning them for possible side-effects due to differences in List implementations. WDYT?
Regarding JavaDoc, I'm happy with current state on this method, except that it mentions {@link ImmutableList#of(Object)}
instead of {@link ImmutableList#of()}
.
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.
Good remark, we should do this 😄.
For the Collections.emptyList()
we can use the same setup as FluxFlatMapUsageCheck.java
. We should do two suggested fixes there, such that it will say: "Do you want to use ImmutableList.of()
or add a SuppressWarnings("...")
"?
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.
Lets create a new PR to create an error prone rule to validate Collections.emptyList() and suggest ImmutableList.of()
And on this PR I will change current refaster template to List.of() -> ImmutableList.of()
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { | ||
return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); | ||
} | ||
} |
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.
Let's add a newline between the class and the comments.
@@ -79,4 +81,32 @@ | |||
ImmutableList<Integer> testStreamToDistinctImmutableList() { | |||
return Stream.of(1).distinct().collect(toImmutableList()); | |||
} | |||
|
|||
List<?> testImmutableListOf() { |
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.
We discussed this offline a bit; this was a hard case to test, but the question was: given the implementation of emptyList()
is there a way we can also test the <T>
with something else then the default Object
.
The implementation:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
Simply returning the default would result in this:
List<Object> test() {
return Collections.emptyList();
}
So what we could do is add the following in the *TestInput.java
:
ImmutableSet<List<?>> testImmutableListOf() {
return ImmutableSet.of(Collections.emptyList(), Collections.<String>emptyList());
}
The second case is of type List<String>
.
So in this case we would test what we discussed the first time. However, not 100% certain this does add a lot. So will leave this as an open question to other reviewers 😄.
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.
:)
I am leaving as is until then
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.
I think it'll be good to make some changes before that. The current state (having two different methods test one Refaster template) is not something we want. In this case testImmutableListOf
and testImmutableListOfTyped
test one Refaster template. We want one test method per Refaster template, so it would be nice to merge these methods in one.
Besides that, we can be more precise with the List<?>
. The default is Object
and in the second example with the <String>emptyList()
, List<Object
would still be good as a return type. In general, it is better to be more precise about returntypes where possible. So I'd suggest to change the ?
and be more precise.
return Collections.emptyList(); | ||
} | ||
|
||
List<String> testImmutableListOfTyped() { |
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.
This is a comment to give some additional context on having multiple test methods for one Refaster template (explanation copied over from here:
Useful to know: if we want to have multiple test cases for one Refaster template, we generally wrap these cases in an
ImmutableSet.of(...)
and return that. This way we have the test cases together in one single method.
Within this class there are other methods that do this. See for example:
ImmutableSet<Duration> testZeroDuration() {
As a result the tests are more concise and it is easier to see how a Refaster template is tested.
return Collections.emptyMap(); | ||
} | ||
|
||
ImmutableSet<Map<?, ?>> testImmutableMapOfN() { |
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.
Since we are only using Strings here as arguments, it's safe to use ImmutableSet<Map<String, String>>
here as return type.
The <?, ?>
is mostly used for templates that work with AssertJ.
|
||
ImmutableSet<Map<?, ?>> testImmutableMapOfN() { | ||
return ImmutableSet.of( | ||
Collections.emptyMap(), |
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.
Ah, this got updated while reviewing. My assumption is that this change is based on this feedback in the other PR. However, there is a small and important distinction to make here.
So now we have one test, that contains multiple test cases for multiple Refaster templates.
The important line from the comment in the other PR: "if we want to have multiple test cases for one Refaster template", we should wrap the test cases in a ImmutableSet
.
Notice the difference that is highlighted.
In summary; the previous state was already in a good shape, because we had a test method with a test case for each Refaster template. If we want to add multiple test cases per Refaster template, we should put the test cases in the same method by using a ImmutableSet
.
Was the assumption correct? Let me know if the explanation is clear 🙂
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.
Now I see that in the other PR my comment wasn't clear enough, which makes it clear that is the reason for making this change. Will make the explanation more explicit next time 🙂.
35dbaca
to
c76a46c
Compare
c76a46c
to
b60d642
Compare
@@ -79,4 +81,32 @@ | |||
ImmutableList<Integer> testStreamToDistinctImmutableList() { | |||
return Stream.of(1).distinct().collect(toImmutableList()); | |||
} | |||
|
|||
List<?> testImmutableListOf() { |
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.
I think it'll be good to make some changes before that. The current state (having two different methods test one Refaster template) is not something we want. In this case testImmutableListOf
and testImmutableListOfTyped
test one Refaster template. We want one test method per Refaster template, so it would be nice to merge these methods in one.
Besides that, we can be more precise with the List<?>
. The default is Object
and in the second example with the <String>emptyList()
, List<Object
would still be good as a return type. In general, it is better to be more precise about returntypes where possible. So I'd suggest to change the ?
and be more precise.
@@ -76,23 +75,23 @@ | |||
return Collections.emptySet(); | |||
} | |||
|
|||
Collection<String> testImmutableSetOfItems1() { | |||
Set<String> testImmutableSetOfItems1() { |
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.
For ImmutableList
and ImmutableMap
templates the name is of format: Immutable{List,Map}OfN
where N is the number.
For ImmutableSet
the naming is a bit different, so I'd suggest to update that to be consistent withImmutableList
and ImmutableMap
. In this case it would be ImmutableSetOf
instead of ImmutableSetOfItems1
.
Sorry, meant to comment but pressed approve 😬 |
* Prefer {@link ImmutableSet#of(Object)} over alternatives that don't communicate the | ||
* immutability of the resulting list at the type level. | ||
*/ | ||
static final class ImmutableSetOfItems1<T> { |
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.
Here there are a few templates that still have Items
in the name. The tests are updated, but here we do not reflect these changes.
@@ -186,4 +187,76 @@ private ImmutableListTemplates() {} | |||
return stream.collect(toImmutableSet()).asList(); | |||
} | |||
} | |||
|
|||
static final class ImmutableListOf<T> { |
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.
For most templates the Javadoc is now good. Only for this case, the emptyList()
I'm not 100% sure. So the List.of(..)
calls uses the ImmutableCollections
under the hood. However, for the emptyList()
this is not the case:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
I'm not sure what to write in the Javadoc there, so also leaving this open for other reviewers to give their input :)
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.
Challenging but very worthwhile PR. Great to see the collaboration 💪
Adding premature review to place comment in a discussion thread that should be considered rather sooner than later.
...b/src/test/resources/tech/picnic/errorprone/bugpatterns/ImmutableListTemplatesTestInput.java
Outdated
Show resolved
Hide resolved
List<T> before() { | ||
return Collections.emptyList(); | ||
} | ||
|
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.
Shall we also consider List.of()
as @BeforeTemplate
here?
@BeforeTemplate | |
List<T> before() { | |
return List.of(); | |
} | |
...e-contrib/src/main/java/tech/picnic/errorprone/refastertemplates/ImmutableListTemplates.java
Outdated
Show resolved
Hide resolved
@@ -186,4 +187,76 @@ private ImmutableListTemplates() {} | |||
return stream.collect(toImmutableSet()).asList(); | |||
} | |||
} | |||
|
|||
static final class ImmutableListOf<T> { |
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.
I think we should be very careful here (also when considering List.of()
as before template, see other comment). We have three different List
implementations with different behavior:
- Existing
@Before Collections.emptyList()
: uses Java'sCollections.EmptyList
which silently ignores calls tosort()
,replaceAll()
andremoveIf()
as there are no elements anyway. - Suggested
@Before List.of()
: uses Java'sImmutableCollections.ListN
which throwsUnsupportedOperationException
on those methods. - Existing
@After ImmutableList.of()
: uses Guava'sImmutableList
which throwsUnsupportedOperationException
on those methods.
My suggestion (after further implementation investigation):
List.of()
and ImmutableList.of()
have the most similar implementation and I would be happy to make that conversion. I would create an ErrorProne
check for Collections.emptyList()
to suggest using ImmutableList.of()
warning them for possible side-effects due to differences in List implementations. WDYT?
Regarding JavaDoc, I'm happy with current state on this method, except that it mentions {@link ImmutableList#of(Object)}
instead of {@link ImmutableList#of()}
.
Fly-by-comment: I was expecting templates for the Immutables library that we're using "only" to find templates dealing with Guava's Also, very nice seeing this collaboration in this repository, exciting times ahead. 😉 |
ImmutableCollection
templates
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.
Thanks for the discussion around Collections.emptyList
. Went over the entire PR now and see the same reasoning for emptySet
and emptyMap
. Apart from this, LGTM.
@@ -186,4 +186,100 @@ private ImmutableListTemplates() {} | |||
return stream.collect(toImmutableSet()).asList(); | |||
} | |||
} | |||
|
|||
/** | |||
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the |
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.
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the | |
* Prefer {@link ImmutableList#of()} over alternatives that don't communicate the |
|
||
@AfterTemplate | ||
ImmutableList<T> after(T e1) { | ||
return ImmutableList.of(e1); |
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.
Strictly speaking the signature uses element
as parameter name. But I actually prefer this for consistency 😄 (Set
also uses e1
for single argument method). Just wanted to point it out.
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.
I also prefer this for consistency :)
static final class ImmutableMapOf<K, V> { | ||
@BeforeTemplate | ||
Map<K, V> before() { | ||
return Collections.emptyMap(); |
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.
Same reasoning as with Collections.emptyList()
this implementation (Collections.EmptyMap
) differs significantly compared to RegularImmutableMap
(e.g. clear()
, replaceAll()
). Would recommend to do an error prone check instead. Also here we can opt for Map.of()
as @BeforeTemplate
instead.
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.
Thanks for the thorough investigation @Badbond! I'm fine with keeping the PR as-is given that I don't expect the difference to bite us in practice, but OTOH an Error Prone check would indeed enable custom error messages. (Though perhaps we should add support for custom error messages to Refaster; this could be done by having RefasterCheck
understand some custom annotation.)
Yet on the other hand, an Error Prone check would avoid the current boilerplate and generalize to >5 arguments.
static final class ImmutableSetOf<T> { | ||
@BeforeTemplate | ||
Set<T> before() { | ||
return Collections.emptySet(); |
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.
Same reasoning as with Collections.emptyList()
(and Map) this implementation (Collections.EmptySet
) differs significantly compared to RegularImmutableSet
(e.g. clear()
, removeIf()
). Would recommend to do an error prone check instead. Also here we can opt for Set.of()
as @BeforeTemplate
instead.
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.
LGTM. Suggested commit message (feel free to change):
Introduce `Immutable{List,Map,Set}#of` Refaster templates
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.
Nice work @mussatto, the templates look good! 🚀
Curious to see how they perform on the Picnic codebase 😄.
Was going over the tests and noticed that we more often use Integer
in tests than String
s.
So pushed a suggestion that changed it for the Immutable{List,Set}
. For the ImmutableMap
I feel that it is nice to have the "k1"
and "v1"
, so leaving that as is. WDYT?
Again, here we do not have a hard rule (yet) that says we have do this. In this case the Integer variant is a bit shorter and overall more used in these Test{Input,Output}
files.
Tweaked the suggested commit message a bit.
ImmutableCollection
templatesImmutableCollection
templates
Might be nice to update PR description a bit based on the new changes btw 🙂 |
Discussed the original rewrites of this PR again, so the following three:
The fact that there are some minor differences between the API's is not that big of a problem. |
I would not call going from silently ignoring to throwing exceptions a subtle/minor difference (reference comment), but I can imagine the thoughts as it is only on a select few methods. For our own internal projects, I won't expect an impact. But in the grand scheme of things when going open-source, I suspect that this might cause problems for some projects. But if the majority agrees on this, I won't hold this back 🙂 |
064f1ac
to
c98357f
Compare
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.
Nice @mussatto !
Made a suggestion with no strong reasoning behind it (other then sorting lexicographical 🤔). WDYT?
831be06
to
d316e23
Compare
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.
Rebased and resolved conflicts; review TBD.
Was going over the tests and noticed that we more often use
Integer
in tests thanString
s.
Historically I switched between these just for some variation 😅.
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.
Rebased and added a commit. I think an Error Prone check which identifies all these different ways in which to create a fixed-size collection might be worthwhile eventually. It could emit suitable customized warnings, identify different ways in which builders can be used, and work for >5 elements.
But this PR is certainly good enough for now, so: let's roll!
Alternative suggested commit message:
Introduce additional `Immutable{List,Map,Set}#of` Refaster templates (#40)
} | ||
|
||
@BeforeTemplate | ||
List<T> before2() { |
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.
We can combine these @BeforeTemplate
methods using Refaster.anyOf
.
static final class ImmutableMapOf<K, V> { | ||
@BeforeTemplate | ||
Map<K, V> before() { | ||
return Collections.emptyMap(); |
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.
Thanks for the thorough investigation @Badbond! I'm fine with keeping the PR as-is given that I don't expect the difference to bite us in practice, but OTOH an Error Prone check would indeed enable custom error messages. (Though perhaps we should add support for custom error messages to Refaster; this could be done by having RefasterCheck
understand some custom annotation.)
Yet on the other hand, an Error Prone check would avoid the current boilerplate and generalize to >5 arguments.
ImmutableList<T> after(T e1, T e2, T e3, T e4, T e5) { | ||
return ImmutableList.of(e1, e2, e3, e4, e5); | ||
} | ||
} |
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.
Location-wise I would probably have located these templates further up in the file, as we generally start with "construction" checks. But since we're planning to auto-sort the templates lexicographically: let's roll with this :)
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.
Based on the other files, I got the idea that we list templates with different number of params overloads at the end of a file.
(I know it is also because you auto-generated some of the templates 😄).
Nonetheless, good point about "construction" checks :).
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.
Indeed, the auto-generated checks in AssertJTemplates
are "special", and indeed at the bottom. (Additional candidates for either an Error Prone check or a Refaster extension.)
static final class ImmutableListOf1<T> { | ||
@BeforeTemplate | ||
List<T> before(T e1) { | ||
return List.of(e1); |
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.
We should also flag Collections.singleton*
methods.
Edit: ah, there's a template for that further up. They should then be merged.
@@ -242,6 +242,109 @@ private ImmutableMapTemplates() {} | |||
} | |||
} | |||
|
|||
/** | |||
* Prefer {@link ImmutableMap#of()} over alternatives that don't communicate the immutability of | |||
* the resulting list at the type level. |
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.
* the resulting list at the type level. | |
* the resulting map at the type level. |
Etc.
* Prefer {@link ImmutableMap#of(Object, Object)} over alternatives that don't communicate the | ||
* immutability of the resulting list at the type level. | ||
*/ | ||
static final class ImmutableMapOf1<K, V> { |
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.
This check should be merged with PairToImmutableMap
.
* Prefer {@link ImmutableMap#of()} over alternatives that don't communicate the immutability of | ||
* the resulting list at the type level. | ||
*/ | ||
static final class ImmutableMapOf<K, V> { |
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.
This one should be merged with EmptyImmutableMap
. Likewise for a number of other templates.
@EnricSala anything else from your side? :) |
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.
🚀
ae27d22
to
920a2e1
Compare
Rebased. |
Introduce templates for
{Set,List,Map}.of(...)
andCollection.{emptyList,emptyMap,emptySet}()
toImmutable{Set,List.Map}.of(...)
from zero up to 5 elements.