checker-framework
checker-framework copied to clipboard
Problem with dataflow inside a lambda during type argument inference
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?
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).
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?
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
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.
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){}
}
}
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