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

Problem with dataflow inside a lambda during type argument inference #6629

Open
roded opened this issue May 29, 2024 · 6 comments
Open

Problem with dataflow inside a lambda during type argument inference #6629

roded opened this issue May 29, 2024 · 6 comments
Assignees

Comments

@roded
Copy link

roded commented May 29, 2024

Trying to bump our Checkerframework version to from 3.42.0 to 3.43.0 and am seeing the following issue which is not clear to me:

NotNull is applied in the package-info.java:

@DefaultQualifier(value = NotNull.class,
    locations = {TypeUseLocation.FIELD, TypeUseLocation.PARAMETER, TypeUseLocation.RETURN})

The following code has started producing the following error:

    public List<DatasetModel> convert(List<VolumeDataset> volumeDatasets) {
        return volumeDatasets.stream().map(dcollectDatasetEntry -> {
            List<DatasetVolserModel> datasetVolserModels = convertVolumeDataset(dcollectDatasetEntry);
            DatasetTypeModel entryTypeModel = convertDcollectEntryType(dcollectDatasetEntry.getType());
            return new DatasetModel(
                dcollectDatasetEntry.getName(),
                entryTypeModel,
                datasetVolserModels,
                dcollectDatasetEntry.getCreationDate(),
                dcollectDatasetEntry.getLastReferenceDate());
        }).collect(Collectors.toList());
    }
error: [type.arguments.not.inferred] Could not infer type arguments for Stream.collect
        }).collect(Collectors.toList());
                  ^
  unsatisfiable constraint: @UnknownInitialization(io.xxx.cdp.api.models.dataset.DatasetModel.class) @NonNull DatasetModel <: @Initialized @Nullable Object @UnknownInitialization(io.xxx.cdp.api.models.dataset.DatasetModel.class) @NonNull DatasetModel <: @Initialized @Nullable Object

Is this expected?
What has changed since 3.42.0 which is causing this issue?

@smillst
Copy link
Member

smillst commented May 30, 2024

Before version 3.43.0, type argument inference errors were suppressed by default. We greatly improved the handling of type argument inference in 3.43.0, so they are no longer suppressed by default. That's why this error is new.

It looks like the problem is that an argument to new DatasetModel(...) has a qualifier of @UnknownInitialization(io.xxx.cdp.api.models.dataset.DatasetModel.class).

@roded
Copy link
Author

roded commented May 30, 2024

Thanks for the reply @smillst.

But I'm still at a loss, and can't make sense of this error.
All of these classes are immutable with private final fields.

    public DatasetModel(@JsonProperty(required = true, value = "datasetName") String datasetName,
                        @JsonProperty(required = true, value = "type") DatasetTypeModel type,
                        @JsonProperty(required = true, value = "volsers") List<DatasetVolserModel> volsers,
                        @JsonProperty("creationDate") @Nullable LocalDate creationDate,
                        @JsonProperty("lastReferenceDate") @Nullable LocalDate lastReferenceDate) {
        this.datasetName = datasetName;
        this.type = type;
        this.volsers = volsers;
        this.creationDate = creationDate;
        this.lastReferenceDate = lastReferenceDate;
    }

What am I missing here? Where can I read up on type argument inference?

@smillst
Copy link
Member

smillst commented May 31, 2024

The problem is an argument to the constructor, not its formal parameters. Try this assigning the constructor to a local variable to see it's type and you should get an error for one or more of the arguments.

            @Initialized DatasetModel d =  new DatasetModel(
                dcollectDatasetEntry.getName(),
                entryTypeModel,
                datasetVolserModels,
                dcollectDatasetEntry.getCreationDate(),
                dcollectDatasetEntry.getLastReferenceDate());
            return d;

Here's an example that is similar to your error:

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.NonNull;

public class Test4 {

  public List<DatasetModel> convert(List< VolumeDataset> volumeDatasets) {
    return volumeDatasets.stream().map(dcollectDatasetEntry -> {
      Object o = method();
      @NonNull @Initialized DatasetModel d = new DatasetModel(o);
      return d;
    }).collect(Collectors.toList());
  }

  @UnknownInitialization Objects method() {
    throw new RuntimeException();
  }

  static class DatasetModel {

    public DatasetModel(Object o) {
    }
  }

  static class VolumeDataset {}
}

Here's are the errors I get:

javacheck -processor nullness ../testcases/src/Test4.java
../testcases/src/Test4.java:13: error: [assignment] incompatible types in assignment.
      @NonNull @Initialized DatasetModel d = new DatasetModel(o);
                                             ^
  found   : @UnderInitialization(Test4.DatasetModel.class) @NonNull DatasetModel
  required: @Initialized @NonNull DatasetModel
../testcases/src/Test4.java:13: error: [argument] incompatible argument for parameter o of DatasetModel constructor.
      @NonNull @Initialized DatasetModel d = new DatasetModel(o);
                                                              ^
  found   : @UnknownInitialization @NonNull Object
  required: @Initialized @NonNull Object
2 errors

@roded
Copy link
Author

roded commented May 31, 2024

Thanks for you attention on this @smillst. Apologies if the misunderstanding is my own.

I'm trying the following sample code:

public class TestModel {

    private final String datasetName;
    private final DatasetTypeModel type;
    private final List<DatasetVolserModel> volsers;
    private final @Nullable LocalDate creationDate;
    private final @Nullable LocalDate lastReferenceDate;

    public TestModel(String datasetName,
                     DatasetTypeModel type,
                     List<DatasetVolserModel> volsers,
                     @Nullable LocalDate creationDate,
                     @Nullable LocalDate lastReferenceDate) {
        this.datasetName = datasetName;
        this.type = type;
        this.volsers = volsers;
        this.creationDate = creationDate;
        this.lastReferenceDate = lastReferenceDate;
    }

    public String getDatasetName() {
        return datasetName;
    }

    public DatasetTypeModel getType() {
        return type;
    }

    public List<DatasetVolserModel> getVolsers() {
        return volsers;
    }

    public @Nullable LocalDate getCreationDate() {
        return creationDate;
    }

    public @Nullable LocalDate getLastReferenceDate() {
        return lastReferenceDate;
    }
}
    public List<TestModel> testConvert(List<VolumeDataset> volumeDatasets) {
        return volumeDatasets.stream().map(dcollectDatasetEntry -> {
            TestModel datasetModel = new TestModel(
                "name",
                DatasetTypeModel.AIX,
                new ArrayList<>(),
                null,
                null);
            return datasetModel;
        }).collect(Collectors.toList());
    }

Produces the error:

error: [type.arguments.not.inferred] Could not infer type arguments for Stream.collect
        }).collect(Collectors.toList());
                  ^
  unsatisfiable constraint: @UnknownInitialization @Nullable TestModel <: @Initialized @Nullable Object @UnknownInitialization @Nullable TestModel <: @Initialized @Nullable Object

Adding @Initialized does seem to solve the issue, but I don't think that's the desired patten of usage.

P.S.
After adding @Initialized, the following error is produced:

error: [type.arguments.not.inferred] Could not infer type arguments for Stream.collect
        }).collect(Collectors.toList());
                  ^
  unsatisfiable constraint: @Initialized @Nullable TestModel <: @Initialized @NonNull TestModel

But this seems like a separate issue related to default bounds settings.

@smillst
Copy link
Member

smillst commented May 31, 2024

Thanks for the test case. This is a bug. Here's a smaller test case that reproduces the problem:

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Issue6629 {

  void method(Stream<String> stream){
     stream.map(x -> {
      Object o = new Object();
      return new Other(o);
    }).collect(Collectors.toList());
  }
  static class Other {
    public Other(Object o){}
  }
}

@smillst smillst changed the title Unclear change of nullness checker behavior in 3.43.0 Problem with dataflow inside a lambda during type argument inference Jun 3, 2024
@agentgt
Copy link

agentgt commented Jun 11, 2024

FWIW @smillst I have encountered a similar error that I can file a new bug if you like and or maybe there is a way to annotate the "sneak throw" technique.

     /**
     * An error friendly {@link Function} for converting properties.
     *
     * @param <T> input type.
     * @param <R> output type.
     * @param <E> error type.
     */
    public interface PropertyFunction<T extends @Nullable Object, R extends @Nullable Object, E extends Exception>
            extends Function<T, R> {

        @Override
        default R apply(T t) {
            try {
                return _apply(t);
            }
            catch (Exception e) {
                // the error happens below here.
                sneakyThrow(e);
                throw new RuntimeException(e);
            }
        }

        /**
         * Apply that throws error.
         * @param t input
         * @return output
         * @throws E if an error happened in function.
         */
        public R _apply(T t) throws E;

        @SuppressWarnings("unchecked")
        private static <E extends Throwable> void sneakyThrow(final Throwable x) throws E {
            throw (E) x;
        }

    }
[type.arguments.not.inferred] Could not infer type arguments for PropertyFunction.sneakyThrow
  unsatisfiable constraint: @UnknownInitialization @Nullable RuntimeException</*Type args not initialized*/> <: @Initialized @NonNull Throwable

I'm not really sure how to properly annotate or suppress the warning (edit I had the suppress in the wrong place so that is why I could not suppress).

EDIT when I suppress the warning I get:

error: StructuralEqualityComparer: unexpected combination:  type: [DECLARED class org.checkerframework.framework.type.AnnotatedTypeMirror$AnnotatedDeclaredType] Object  supertype: [TYPEVAR class org.checkerframework.framework.type.AnnotatedTypeMirror$AnnotatedTypeVariable] R extends Object

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

No branches or pull requests

3 participants