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

TYPE_USE annotations were being ignored #115

Merged
merged 1 commit into from Jun 12, 2022

Conversation

Randgalt
Copy link
Owner

Java's DAG for annotations processors doesn't contain TYPE_USE annotations
on the Element for some reason. However, they are on the type. So, use the
type instead.

Note due to limitations of JavaPoet this doesn't fix TYPE_USE annotations on
parameterized types or array components. If we want to address those we will need
changes in JavaPoet which has been dormant for a very long time.

Fixes #113
Relates to #111

@Randgalt
Copy link
Owner Author

cc @sesamzoo and @agentgt

Java's DAG for annotations processors doesn't contain `TYPE_USE` annotations
on the Element for some reason. However, they are on the type. So, use the
type instead.

Note due to limitations of JavaPoet this doesn't fix `TYPE_USE` annotations on
parameterized types or array components. If we want to address those we will need
changes in JavaPoet which has been dormant for a very long time.

Fixes #113
Relates to #111
@Randgalt Randgalt force-pushed the jordanz/fix-type-use-annotations branch from d3e276e to b7eef30 Compare May 22, 2022 11:54
@Randgalt
Copy link
Owner Author

FYI - for the standard @RecordBuilder you get:

// Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder
package io.soabase.recordbuilder.test.typeuse;

import java.util.AbstractMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.annotation.processing.Generated;

@Generated("io.soabase.recordbuilder.core.RecordBuilder")
public class MyRecordBuilder {
    private String nonNullS;

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    private MyRecordBuilder() {
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    private MyRecordBuilder(String nonNullS) {
        this.nonNullS = nonNullS;
    }

    /**
     * Static constructor/builder. Can be used instead of new MyRecord(...)
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyRecord MyRecord(@TypeUseNonNull String nonNullS) {
        return new MyRecord(nonNullS);
    }

    /**
     * Return a new builder with all fields set to default Java values
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyRecordBuilder builder() {
        return new MyRecordBuilder();
    }

    /**
     * Return a new builder with all fields set to the values taken from the given record instance
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyRecordBuilder builder(MyRecord from) {
        return new MyRecordBuilder(from.nonNullS());
    }

    /**
     * Return a "with"er for an existing record instance
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyRecordBuilder.With from(MyRecord from) {
        return new _FromWith(from);
    }

    /**
     * Return a stream of the record components as map entries keyed with the component name and the value as the component value
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static Stream<Map.Entry<String, Object>> stream(MyRecord record) {
        return Stream.of(new AbstractMap.SimpleImmutableEntry<>("nonNullS", record.nonNullS()));
    }

    /**
     * Return a new record instance with all fields set to the current values in this builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public MyRecord build() {
        return new MyRecord(nonNullS);
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public String toString() {
        return "MyRecordBuilder[nonNullS=" + nonNullS + "]";
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public int hashCode() {
        return Objects.hash(nonNullS);
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public boolean equals(Object o) {
        return (this == o) || ((o instanceof MyRecordBuilder r)
                && Objects.equals(nonNullS, r.nonNullS));
    }

    /**
     * Set a new value for the {@code nonNullS} record component in the builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public MyRecordBuilder nonNullS(@TypeUseNonNull String nonNullS) {
        this.nonNullS = nonNullS;
        return this;
    }

    /**
     * Return the current value for the {@code nonNullS} record component in the builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @TypeUseNonNull
    public String nonNullS() {
        return nonNullS;
    }

    /**
     * Add withers to {@code MyRecord}
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public interface With {
        /**
         * Return the current value for the {@code nonNullS} record component in the builder
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        @TypeUseNonNull
        String nonNullS();

        /**
         * Return a new record builder using the current values
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyRecordBuilder with() {
            return new MyRecordBuilder(nonNullS());
        }

        /**
         * Return a new record built from the builder passed to the given consumer
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyRecord with(Consumer<MyRecordBuilder> consumer) {
            MyRecordBuilder builder = with();
            consumer.accept(builder);
            return builder.build();
        }

        /**
         * Return a new instance of {@code MyRecord} with a new value for {@code nonNullS}
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyRecord withNonNullS(@TypeUseNonNull String nonNullS) {
            return new MyRecord(nonNullS);
        }
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    private static final class _FromWith implements MyRecordBuilder.With {
        private final MyRecord from;

        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        private _FromWith(MyRecord from) {
            this.from = from;
        }

        @Override
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        public String nonNullS() {
            return from.nonNullS();
        }
    }
}

@Randgalt
Copy link
Owner Author

For RecordBuilderFull you get:

// Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder
package io.soabase.recordbuilder.test.typeuse;

import io.soabase.recordbuilder.core.RecordBuilderGenerated;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.processing.Generated;

@Generated("io.soabase.recordbuilder.core.RecordBuilder")
@RecordBuilderGenerated
public class MyFullRecordBuilder {
    private String nonNullS;

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    private MyFullRecordBuilder() {
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    private MyFullRecordBuilder(String nonNullS) {
        this.nonNullS = nonNullS;
    }

    /**
     * Static constructor/builder. Can be used instead of new MyFullRecord(...)
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyFullRecord MyFullRecord(@TypeUseNonNull String nonNullS) {
        return new MyFullRecord(nonNullS);
    }

    /**
     * Return a new builder with all fields set to default Java values
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyFullRecordBuilder builder() {
        return new MyFullRecordBuilder();
    }

    /**
     * Return a new builder with all fields set to the values taken from the given record instance
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyFullRecordBuilder builder(MyFullRecord from) {
        return new MyFullRecordBuilder(from.nonNullS());
    }

    /**
     * Return a "with"er for an existing record instance
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static MyFullRecordBuilder.With from(MyFullRecord from) {
        return new _FromWith(from);
    }

    /**
     * Return a stream of the record components as map entries keyed with the component name and the value as the component value
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public static Stream<Map.Entry<String, Object>> stream(MyFullRecord record) {
        return Stream.of(new AbstractMap.SimpleImmutableEntry<>("nonNullS", record.nonNullS()));
    }

    /**
     * Return a new record instance with all fields set to the current values in this builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public MyFullRecord build() {
        return new MyFullRecord(nonNullS);
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public String toString() {
        return "MyFullRecordBuilder[nonNullS=" + nonNullS + "]";
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public int hashCode() {
        return Objects.hash(nonNullS);
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @Override
    public boolean equals(Object o) {
        return (this == o) || ((o instanceof MyFullRecordBuilder r)
                && Objects.equals(nonNullS, r.nonNullS));
    }

    /**
     * Set a new value for the {@code nonNullS} record component in the builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    public MyFullRecordBuilder nonNullS(@TypeUseNonNull String nonNullS) {
        this.nonNullS = nonNullS;
        return this;
    }

    /**
     * Return the current value for the {@code nonNullS} record component in the builder
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @TypeUseNonNull
    public String nonNullS() {
        return nonNullS;
    }

    /**
     * Add withers to {@code MyFullRecord}
     */
    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @RecordBuilderGenerated
    public interface With {
        /**
         * Return the current value for the {@code nonNullS} record component in the builder
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        @TypeUseNonNull
        String nonNullS();

        /**
         * Return a new record builder using the current values
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyFullRecordBuilder with() {
            return new MyFullRecordBuilder(nonNullS());
        }

        /**
         * Return a new record built from the builder passed to the given consumer
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyFullRecord with(java.util.function.Consumer<MyFullRecordBuilder> consumer) {
            MyFullRecordBuilder builder = with();
            consumer.accept(builder);
            return builder.build();
        }

        /**
         * Return a new instance of {@code MyFullRecord} with a new value for {@code nonNullS}
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default MyFullRecord withNonNullS(@TypeUseNonNull String nonNullS) {
            return new MyFullRecord(nonNullS);
        }

        /**
         * Map record components into a new object
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default <R> R map(Function<R> proc) {
            return proc.apply(nonNullS());
        }

        /**
         * Perform an operation on record components
         */
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        default void accept(Consumer proc) {
            proc.apply(nonNullS());
        }

        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        @FunctionalInterface
        interface Function<R> {
            R apply(@TypeUseNonNull String nonNullS);
        }

        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        @FunctionalInterface
        interface Consumer {
            void apply(@TypeUseNonNull String nonNullS);
        }
    }

    @Generated("io.soabase.recordbuilder.core.RecordBuilder")
    @RecordBuilderGenerated
    private static final class _FromWith implements MyFullRecordBuilder.With {
        private final MyFullRecord from;

        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        private _FromWith(MyFullRecord from) {
            this.from = from;
        }

        @Override
        @Generated("io.soabase.recordbuilder.core.RecordBuilder")
        public String nonNullS() {
            return from.nonNullS();
        }
    }
}

@Randgalt
Copy link
Owner Author

Randgalt commented Jun 2, 2022

@sesamzoo and @agentgt any comments before I merge?

@agentgt
Copy link

agentgt commented Jun 4, 2022

I’ll have a look Monday. Sorry I was sick the last few days.

@sesamzoo
Copy link

sesamzoo commented Jun 9, 2022

@Randgalt, with this PR my example from #113 works as expected.

@agentgt
Copy link

agentgt commented Jun 9, 2022

@Randgalt it looks right. I have not pulled the code yet and tested things like arrays and inner classes but it looks correct. Even if it doesn't handle those cases yet this looks like the correct behavior of propagating the type annotations.

BTW you might want to test and or be careful with the Eclipse compiler annotation processor. IIRC it has an issue where it will propagate @NonNull-like TYPE_USE annotations even if they are not on the type! Basically the nonnull analysis part of the compiler inserts it in. This is kind of a bug with Eclipse. The irony is annotation processors that ignore TYPE_USE do not have this problem but those that do can have issues. It is possible @stephan-herrmann has fixed this.

Anyway Google Auto Value is one of the few projects I know that does deal with TYPE_USE annotations correctly (and now this project will as well 😄 ) and they have issues with Eclipse (see):

I'm not saying you need to deal with it but just be aware of it.

@Randgalt Randgalt merged commit d3c1bb3 into master Jun 12, 2022
@Randgalt Randgalt deleted the jordanz/fix-type-use-annotations branch June 12, 2022 07:56
@Randgalt Randgalt added this to the v34 milestone Jun 12, 2022
Randgalt added a commit that referenced this pull request Jun 13, 2022
This reverts commit d3c1bb3.

A bug was found - the PR needs more work
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Checker framework's NonNull annotation is ignored
3 participants