8170410: inference: javac doesn't implement 18.2.5 correctly

Javac does not generate constraints of the kind 'throws alpha' as described in the spec

Reviewed-by: vromero, dlsmith
This commit is contained in:
Maurizio Cimadamore 2016-12-05 19:00:56 +00:00
parent ae8ace7912
commit 04f289629a
4 changed files with 87 additions and 55 deletions

View File

@ -1869,6 +1869,12 @@ public abstract class Type extends AnnoConstruct implements TypeMirror {
*/
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 @@ public abstract class Type extends AnnoConstruct implements TypeMirror {
/** 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 abstract class Type extends AnnoConstruct implements TypeMirror {
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 @@ public abstract class Type extends AnnoConstruct implements TypeMirror {
//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 @@ public abstract class Type extends AnnoConstruct implements TypeMirror {
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 @@ public abstract class Type extends AnnoConstruct implements TypeMirror {
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 abstract class Type extends AnnoConstruct implements TypeMirror {
}
}
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;
}
}

View File

@ -2494,6 +2494,11 @@ public class Attr extends JCTree.Visitor {
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 @@ public class Attr extends JCTree.Visitor {
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());
}
}

View File

@ -627,12 +627,11 @@ public class Infer {
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 @@ public class Infer {
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;
}

View File

@ -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 ""; }
}