8170410: inference: javac doesn't implement 18.2.5 correctly
Summary: javac does not generate constraints of the kind 'throws alpha' as described in the spec
Reviewed-by: vromero, dlsmith
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java Mon Dec 05 19:42:42 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java Mon Dec 05 19:00:56 2016 +0000
@@ -1869,6 +1869,12 @@
*/
public static class UndetVar extends DelegatedType {
+ enum Kind {
+ NORMAL,
+ CAPTURED,
+ THROWS;
+ }
+
/** Inference variable change listener. The listener method is called
* whenever a change to the inference variable's bounds occurs
*/
@@ -1929,6 +1935,8 @@
/** inference variable's change listener */
public UndetVarListener listener = null;
+ Kind kind;
+
@Override
public <R,S> R accept(Type.Visitor<R,S> v, S s) {
return v.visitUndetVar(this, s);
@@ -1937,6 +1945,9 @@
public UndetVar(TypeVar origin, UndetVarListener listener, Types types) {
// This is a synthesized internal type, so we cannot annotate it.
super(UNDETVAR, origin);
+ this.kind = origin.isCaptured() ?
+ Kind.CAPTURED :
+ Kind.NORMAL;
this.listener = listener;
bounds = new EnumMap<>(InferenceBound.class);
List<Type> declaredBounds = types.getBounds(origin);
@@ -1948,6 +1959,10 @@
//add bound works in reverse order
addBound(InferenceBound.UPPER, t, types, true);
}
+ if (origin.isCaptured() && !origin.lower.hasTag(BOT)) {
+ //add lower bound if needed
+ addBound(InferenceBound.LOWER, origin.lower, types, true);
+ }
}
@DefinedBy(Api.LANGUAGE_MODEL)
@@ -1977,6 +1992,14 @@
return result;
}
+ public void setThrow() {
+ if (this.kind == Kind.CAPTURED) {
+ //invalid state transition
+ throw new IllegalStateException();
+ }
+ this.kind = Kind.THROWS;
+ }
+
/**
* Returns a new copy of this undet var.
*/
@@ -2062,17 +2085,29 @@
addBound(ib, bound, types, false);
}
- protected void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
- Type bound2 = bound.map(toTypeVarMap).baseType();
- List<Type> prevBounds = bounds.get(ib);
- if (bound == qtype) return;
- for (Type b : prevBounds) {
- //check for redundancy - use strict version of isSameType on tvars
- //(as the standard version will lead to false positives w.r.t. clones ivars)
- if (types.isSameType(b, bound2, true)) return;
+ @SuppressWarnings("fallthrough")
+ private void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
+ if (kind == Kind.CAPTURED && !update) {
+ //Captured inference variables bounds must not be updated during incorporation,
+ //except when some inference variable (beta) has been instantiated in the
+ //right-hand-side of a 'C<alpha> = capture(C<? extends/super beta>) constraint.
+ if (bound.hasTag(UNDETVAR) && !((UndetVar)bound).isCaptured()) {
+ //If the new incoming bound is itself a (regular) inference variable,
+ //then we are allowed to propagate this inference variable bounds to it.
+ ((UndetVar)bound).addBound(ib.complement(), this, types, false);
+ }
+ } else {
+ Type bound2 = bound.map(toTypeVarMap).baseType();
+ List<Type> prevBounds = bounds.get(ib);
+ if (bound == qtype) return;
+ for (Type b : prevBounds) {
+ //check for redundancy - use strict version of isSameType on tvars
+ //(as the standard version will lead to false positives w.r.t. clones ivars)
+ if (types.isSameType(b, bound2, true)) return;
+ }
+ bounds.put(ib, prevBounds.prepend(bound2));
+ notifyBoundChange(ib, bound2, false);
}
- bounds.put(ib, prevBounds.prepend(bound2));
- notifyBoundChange(ib, bound2, false);
}
//where
TypeMapping<Void> toTypeVarMap = new TypeMapping<Void>() {
@@ -2128,46 +2163,12 @@
}
}
- public boolean isCaptured() {
- return false;
- }
- }
-
- /**
- * This class is used to represent synthetic captured inference variables
- * that can be generated during nested generic method calls. The only difference
- * between these inference variables and ordinary ones is that captured inference
- * variables cannot get new bounds through incorporation.
- */
- public static class CapturedUndetVar extends UndetVar {
-
- public CapturedUndetVar(CapturedType origin, UndetVarListener listener, Types types) {
- super(origin, listener, types);
- if (!origin.lower.hasTag(BOT)) {
- addBound(InferenceBound.LOWER, origin.lower, types, true);
- }
+ public final boolean isCaptured() {
+ return kind == Kind.CAPTURED;
}
- @Override
- public void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
- if (update) {
- //only change bounds if request comes from substBounds
- super.addBound(ib, bound, types, update);
- }
- else if (bound.hasTag(UNDETVAR) && !((UndetVar) bound).isCaptured()) {
- ((UndetVar) bound).addBound(ib.complement(), this, types, false);
- }
- }
-
- @Override
- public boolean isCaptured() {
- return true;
- }
-
- public UndetVar dup(Types types) {
- UndetVar uv2 = new CapturedUndetVar((CapturedType)qtype, listener, types);
- dupTo(uv2, types);
- return uv2;
+ public final boolean isThrows() {
+ return kind == Kind.THROWS;
}
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Mon Dec 05 19:42:42 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Mon Dec 05 19:00:56 2016 +0000
@@ -2494,6 +2494,11 @@
List<Type> thrownTypes = resultInfo.checkContext.inferenceContext().asUndetVars(lambdaType.getThrownTypes());
chk.unhandled(inferredThrownTypes, thrownTypes);
+
+ //18.2.5: "In addition, for all j (1 <= j <= n), the constraint reduces to the bound throws Ej"
+ thrownTypes.stream()
+ .filter(t -> t.hasTag(UNDETVAR))
+ .forEach(t -> ((UndetVar)t).setThrow());
}
checkAccessibleTypes(that, localEnv, resultInfo.checkContext.inferenceContext(), lambdaType, currentTarget);
@@ -3074,6 +3079,10 @@
if (chk.unhandled(refType.getThrownTypes(), thrownTypes).nonEmpty()) {
log.error(tree, "incompatible.thrown.types.in.mref", refType.getThrownTypes());
}
+ //18.2.5: "In addition, for all j (1 <= j <= n), the constraint reduces to the bound throws Ej"
+ thrownTypes.stream()
+ .filter(t -> t.hasTag(UNDETVAR))
+ .forEach(t -> ((UndetVar)t).setThrow());
}
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java Mon Dec 05 19:42:42 2016 +0100
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java Mon Dec 05 19:00:56 2016 +0000
@@ -627,12 +627,11 @@
TypeMapping<Void> fromTypeVarFun = new TypeMapping<Void>() {
@Override
public Type visitTypeVar(TypeVar tv, Void aVoid) {
- return new UndetVar(tv, incorporationEngine(), types);
- }
-
- @Override
- public Type visitCapturedType(CapturedType t, Void aVoid) {
- return new CapturedUndetVar(t, incorporationEngine(), types);
+ UndetVar uv = new UndetVar(tv, incorporationEngine(), types);
+ if ((tv.tsym.flags() & Flags.THROWS) != 0) {
+ uv.setThrow();
+ }
+ return uv;
}
};
@@ -1463,7 +1462,7 @@
THROWS(InferenceBound.UPPER) {
@Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
- if ((t.qtype.tsym.flags() & Flags.THROWS) == 0) {
+ if (!t.isThrows()) {
//not a throws undet var
return false;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/generics/inference/8170410/T8170410.java Mon Dec 05 19:00:56 2016 +0000
@@ -0,0 +1,23 @@
+/*
+ * @test
+ * @bug 8170410
+ * @summary inference: javac doesn't implement 18.2.5 correctly
+ * @compile T8170410.java
+ */
+
+class T8170410 {
+ interface CheckedSupplier<T extends Throwable, R> {
+ R get() throws T;
+ }
+
+ static <T extends Throwable, R> CheckedSupplier<T, R> checked(CheckedSupplier<T, R> checkedSupplier) {
+ return checkedSupplier;
+ }
+
+ static void test() {
+ checked(() -> null).get();
+ checked(T8170410::m).get();
+ }
+
+ static String m() { return ""; }
+}