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

Introduce MoreTypes utility class #234

Merged
merged 4 commits into from
Oct 25, 2022
Merged

Conversation

Stephan202
Copy link
Member

@Stephan202 Stephan202 commented Sep 11, 2022

Suggested commit message:

Introduce `MoreTypes` utility class (#234)

The static methods of this class allow one to construct complex types,
against which expression types can subsequently be matched.

I'll also push these changes to #224, to show how they can simplify that PR.

@Stephan202 Stephan202 added this to the 0.2.1 milestone Sep 11, 2022
@Stephan202 Stephan202 requested a review from rickie September 11, 2022 18:07
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-moretypes branch from 7cda41b to 7990365 Compare September 18, 2022 14:05
@rickie rickie modified the milestones: 0.3.0, 0.4.0 Sep 19, 2022
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-moretypes branch from 7990365 to c267b8f Compare October 2, 2022 12:38
@Stephan202
Copy link
Member Author

Rebased and resolved conflict.

@rickie rickie modified the milestones: 0.4.0, 0.5.0 Oct 5, 2022
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-moretypes branch from c267b8f to 7602247 Compare October 15, 2022 11:55
@rickie rickie requested a review from werli October 19, 2022 15:41
@rickie
Copy link
Member

rickie commented Oct 19, 2022

I'm wondering whether we should make the check that uses MoreTypes more generic. We can have a checker that contains a list of Suppliers that specify types that we don't want to have nested. Now we (almost) have two checks that uses this type. I think we can come up with many more types that we want to disallow.

Within that check, we could give different error messages based on the type.

Instead of double nested types like Optional<Optional<?>> we could (just as an example) disallow Optional<Flux<?>>. Apart from the weird example, we can maybe think of some use cases.

We could also do this later though.

(Started reviewing this one).

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

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

Added a commit.

Have some suggestions and questions 😄.
The PR itself is very cool! Nice one to review :).

private MoreTypes() {}

/**
* Creates a supplier of the type with the given fully qualified name.
Copy link
Member

Choose a reason for hiding this comment

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

Any reason for not using {@link Supplier}? :)

/**
* A set of helper methods which together define a DSL for defining {@link Type types}.
*
* <p>These methods are meant to be statically imported. Example usage:
Copy link
Member

Choose a reason for hiding this comment

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

Let's add this class to StaticImport.

* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> supOf(Supplier<Type> type) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public static Supplier<Type> supOf(Supplier<Type> type) {
public static Supplier<Type> superOf(Supplier<Type> type) {

I think this makes the difference between sub and sup a bit more clear. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

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

I liked the symmetry, but: sure :)

// method name. But the DSL wouldn't look as nice that way.
@SafeVarargs
@SuppressWarnings("varargs")
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
public static Supplier<Type> genericWithParams(Supplier<Type> type, Supplier<Type>... typeArgs) {

Copy link
Member

Choose a reason for hiding this comment

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

To me, generic could be a bit more descriptive. One needs to really dive into the MoreTypes before understanding how this works. genericWithParams would be a nice option.
Another option would be genericType{,WithParams} with String as first parameter. However, in the current situation we always pass Types as parameter.

I know MoreTypes.genericType(...... has a little duplication, but we statically import every method here of course.

But the DSL wouldn't look as nice that way.

IMHO the DSL is very concise (even if we would make some of the method names a little longer).

Copy link
Member

Choose a reason for hiding this comment

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

The given type should be a generic type

In EP I see checks for this:

boolean isGeneric = typeParams != null && !typeParams.isEmpty();

Maybe it'd be nice to add a check for that in this method? Using checkArgument we can enforce that others use this DSL correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not yet feeling the first suggestion: in practice the code will look like generic(type("fqcn"), ...), which should be clear enough. The class also has only a few methods, and there's an example at the top, which shows how this method can be used.

Copy link
Member Author

Choose a reason for hiding this comment

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

W.r.t. the second suggestion: since the type evaluation is deferred, I'm not sure an IllegalArgumentException is warranted; perhaps an IllegalStateException would fit. Even so: the type might not have a matching set of generic type parameters due to an unexpected type on the classpath (perhaps due to an unsupported dependency version), which IMHO should not cause an error, but simply cause the expression to yield false.

final class MoreTypesTest {
private static final ImmutableSet<Supplier<Type>> TYPES =
ImmutableSet.of(
type("java.lang.Nonexistent"),
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should have a test that verifies use cases like this:

  private static Stream<Arguments> incorrectTypesTestCases() {
    return Stream.of(
        arguments(type("")),
        arguments(type("java.lang.Nonexistent")),
        arguments(generic(type("java.util.Integer"), unbound())));
        ...
  }

Copy link
Member Author

Choose a reason for hiding this comment

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

The first one throws an exception. The third can be added to the list here, as that will verify that (a) it doesn't match and (b) doesn't throw an exception. (Which I think it shouldn't, for the reason mentioned above. That will of course feel weird for java.util.Integer, but not for custom.package.TypeThatWasRefactoredToHaveADifferentNumberOfTypeArgs.)

Copy link
Member

Choose a reason for hiding this comment

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

The first one throws an exception.

Together with the other proposed change, that would be exactly what we could test 😄.

I now understand why it wouldn't be nice to add these extra checks, so I'm fine to leave it as is :).

@rickie
Copy link
Member

rickie commented Oct 20, 2022

Rebased and added a small commit.

@Stephan202 Stephan202 force-pushed the sschroevers/introduce-moretypes branch from 26a75f2 to df41c50 Compare October 21, 2022 22:32
Copy link
Member Author

@Stephan202 Stephan202 left a 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. Tnx for the critical review!

We could also do this later though.

Yeah, let's defer the generalization idea; need to think how useful/desirable that is (for example, on the face of it disallowing Flux<Flux<?>> seems to make sense, until one realizes that this type is produced with Flux#groupBy. The same might hold in a few other cases.)

private MoreTypes() {}

/**
* Creates a supplier of the type with the given fully qualified class name.
Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't need to be a class.

Suggested change
* Creates a supplier of the type with the given fully qualified class name.
* Creates a supplier of the type with the given fully qualified name.

@@ -20,7 +20,7 @@ jobs:
- name: Check out code
uses: actions/[email protected]
- name: Set up JDK
uses: actions/setup-java@v3.5.1
uses: actions/setup-java@v3.6.0
Copy link
Member Author

Choose a reason for hiding this comment

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

We have some stray commits here; will rebase.

// method name. But the DSL wouldn't look as nice that way.
@SafeVarargs
@SuppressWarnings("varargs")
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not yet feeling the first suggestion: in practice the code will look like generic(type("fqcn"), ...), which should be clear enough. The class also has only a few methods, and there's an example at the top, which shows how this method can be used.

// method name. But the DSL wouldn't look as nice that way.
@SafeVarargs
@SuppressWarnings("varargs")
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
Copy link
Member Author

Choose a reason for hiding this comment

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

W.r.t. the second suggestion: since the type evaluation is deferred, I'm not sure an IllegalArgumentException is warranted; perhaps an IllegalStateException would fit. Even so: the type might not have a matching set of generic type parameters due to an unexpected type on the classpath (perhaps due to an unsupported dependency version), which IMHO should not cause an error, but simply cause the expression to yield false.

* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> supOf(Supplier<Type> type) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I liked the symmetry, but: sure :)

final class MoreTypesTest {
private static final ImmutableSet<Supplier<Type>> TYPES =
ImmutableSet.of(
type("java.lang.Nonexistent"),
Copy link
Member Author

Choose a reason for hiding this comment

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

The first one throws an exception. The third can be added to the list here, as that will verify that (a) it doesn't match and (b) doesn't throw an exception. (Which I think it shouldn't, for the reason mentioned above. That will of course feel weird for java.util.Integer, but not for custom.package.TypeThatWasRefactoredToHaveADifferentNumberOfTypeArgs.)

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

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

Thanks for the explanations!

Tweaks look good to me 😄.

final class MoreTypesTest {
private static final ImmutableSet<Supplier<Type>> TYPES =
ImmutableSet.of(
type("java.lang.Nonexistent"),
Copy link
Member

Choose a reason for hiding this comment

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

The first one throws an exception.

Together with the other proposed change, that would be exactly what we could test 😄.

I now understand why it wouldn't be nice to add these extra checks, so I'm fine to leave it as is :).

@rickie rickie requested review from oxkitsune and ibabiankou and removed request for werli October 23, 2022 15:40
Copy link

@ibabiankou ibabiankou left a comment

Choose a reason for hiding this comment

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

Looks good 👍 Don't have much to add, nothing caught my eye 🤷‍♂️

return null;
}

return state.getType(baseType, /* isArray= */ false, params);

Choose a reason for hiding this comment

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

isArray is always false because Suppliers.arrayOf(...) should be used to describe that type, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's been a while since I wrote this, but indeed, I think Suppliers.arrayOf(...) would then be used to wrap the result of this method. 👍

@rickie rickie removed the request for review from oxkitsune October 25, 2022 12:27
Stephan202 and others added 4 commits October 25, 2022 16:55
The static methods of this class allow one to construct complex types,
against which expression types can subsequently be matched.
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-moretypes branch from df41c50 to 79ee60b Compare October 25, 2022 15:07
Copy link
Member Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Rebased; will merge once built.

return null;
}

return state.getType(baseType, /* isArray= */ false, params);
Copy link
Member Author

Choose a reason for hiding this comment

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

It's been a while since I wrote this, but indeed, I think Suppliers.arrayOf(...) would then be used to wrap the result of this method. 👍

@Stephan202 Stephan202 merged commit 92f2b0a into master Oct 25, 2022
@Stephan202 Stephan202 deleted the sschroevers/introduce-moretypes branch October 25, 2022 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants