8012238: Nested method capture and inference

8008200: java/lang/Class/asSubclass/BasicUnit.java fails to compile

Inference support should be more flexible w.r.t. nested method calls returning captured types

Reviewed-by: jjg, vromero
This commit is contained in:
Maurizio Cimadamore 2013-07-17 14:11:41 +01:00
parent 99b0413d48
commit e92a56fade
7 changed files with 250 additions and 16 deletions

View file

@ -1452,7 +1452,7 @@ public abstract class Type implements TypeMirror {
} }
/** inference variable bounds */ /** inference variable bounds */
private Map<InferenceBound, List<Type>> bounds; protected Map<InferenceBound, List<Type>> bounds;
/** inference variable's inferred type (set from Infer.java) */ /** inference variable's inferred type (set from Infer.java) */
public Type inst = null; public Type inst = null;
@ -1520,7 +1520,11 @@ public abstract class Type implements TypeMirror {
} }
/** add a bound of a given kind - this might trigger listener notification */ /** add a bound of a given kind - this might trigger listener notification */
public void addBound(InferenceBound ib, Type bound, Types types) { public final void addBound(InferenceBound ib, Type bound, Types types) {
addBound(ib, bound, types, false);
}
protected void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
Type bound2 = boundMap.apply(bound); Type bound2 = boundMap.apply(bound);
List<Type> prevBounds = bounds.get(ib); List<Type> prevBounds = bounds.get(ib);
for (Type b : prevBounds) { for (Type b : prevBounds) {
@ -1575,7 +1579,7 @@ public abstract class Type implements TypeMirror {
bounds.put(ib, newBounds.toList()); bounds.put(ib, newBounds.toList());
//step 3 - for each dependency, add new replaced bound //step 3 - for each dependency, add new replaced bound
for (Type dep : deps) { for (Type dep : deps) {
addBound(ib, types.subst(dep, from, to), types); addBound(ib, types.subst(dep, from, to), types, true);
} }
} }
} finally { } finally {
@ -1591,6 +1595,39 @@ public abstract class Type implements TypeMirror {
listener.varChanged(this, ibs); listener.varChanged(this, ibs);
} }
} }
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, Types types) {
super(origin, types);
if (!origin.lower.hasTag(BOT)) {
bounds.put(InferenceBound.LOWER, List.of(origin.lower));
}
}
@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);
}
}
@Override
public boolean isCaptured() {
return true;
}
} }
/** Represents NONE. /** Represents NONE.

View file

@ -4417,9 +4417,7 @@ public class Attr extends JCTree.Visitor {
} }
private Type capture(Type type) { private Type capture(Type type) {
//do not capture free types return types.capture(type);
return resultInfo.checkContext.inferenceContext().free(type) ?
type : types.capture(type);
} }
private void validateTypeAnnotations(JCTree tree) { private void validateTypeAnnotations(JCTree tree) {

View file

@ -159,7 +159,8 @@ public class Infer {
!warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) { !warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) {
//inject return constraints earlier //inject return constraints earlier
checkWithinBounds(inferenceContext, warn); //propagation checkWithinBounds(inferenceContext, warn); //propagation
generateReturnConstraints(resultInfo, mt, inferenceContext); Type newRestype = generateReturnConstraints(resultInfo, mt, inferenceContext);
mt = (MethodType)types.createMethodTypeWithReturn(mt, newRestype);
//propagate outwards if needed //propagate outwards if needed
if (resultInfo.checkContext.inferenceContext().free(resultInfo.pt)) { if (resultInfo.checkContext.inferenceContext().free(resultInfo.pt)) {
//propagate inference context outwards and exit //propagate inference context outwards and exit
@ -209,9 +210,20 @@ public class Infer {
* call occurs in a context where a type T is expected, use the expected * call occurs in a context where a type T is expected, use the expected
* type to derive more constraints on the generic method inference variables. * type to derive more constraints on the generic method inference variables.
*/ */
void generateReturnConstraints(Attr.ResultInfo resultInfo, Type generateReturnConstraints(Attr.ResultInfo resultInfo,
MethodType mt, InferenceContext inferenceContext) { MethodType mt, InferenceContext inferenceContext) {
Type qtype1 = inferenceContext.asFree(mt.getReturnType()); Type from = mt.getReturnType();
if (mt.getReturnType().containsAny(inferenceContext.inferencevars) &&
resultInfo.checkContext.inferenceContext() != emptyContext) {
from = types.capture(from);
//add synthetic captured ivars
for (Type t : from.getTypeArguments()) {
if (t.hasTag(TYPEVAR) && ((TypeVar)t).isCaptured()) {
inferenceContext.addVar((TypeVar)t);
}
}
}
Type qtype1 = inferenceContext.asFree(from);
Type to = returnConstraintTarget(qtype1, resultInfo.pt); Type to = returnConstraintTarget(qtype1, resultInfo.pt);
Assert.check(allowGraphInference || !resultInfo.checkContext.inferenceContext().free(to), Assert.check(allowGraphInference || !resultInfo.checkContext.inferenceContext().free(to),
"legacy inference engine cannot handle constraints on both sides of a subtyping assertion"); "legacy inference engine cannot handle constraints on both sides of a subtyping assertion");
@ -224,6 +236,7 @@ public class Infer {
.setMessage("infer.no.conforming.instance.exists", .setMessage("infer.no.conforming.instance.exists",
inferenceContext.restvars(), mt.getReturnType(), to); inferenceContext.restvars(), mt.getReturnType(), to);
} }
return from;
} }
Type returnConstraintTarget(Type from, Type to) { Type returnConstraintTarget(Type from, Type to) {
@ -436,7 +449,9 @@ public class Infer {
EnumSet<IncorporationStep> incorporationSteps = allowGraphInference ? EnumSet<IncorporationStep> incorporationSteps = allowGraphInference ?
incorporationStepsGraph : incorporationStepsLegacy; incorporationStepsGraph : incorporationStepsLegacy;
for (IncorporationStep is : incorporationSteps) { for (IncorporationStep is : incorporationSteps) {
is.apply(uv, inferenceContext, warn); if (is.accepts(uv, inferenceContext)) {
is.apply(uv, inferenceContext, warn);
}
} }
} }
if (!mlistener.changed || !allowGraphInference) break; if (!mlistener.changed || !allowGraphInference) break;
@ -527,6 +542,11 @@ public class Infer {
} }
} }
} }
@Override
boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
//applies to all undetvars
return true;
}
}, },
/** /**
* Check consistency of equality constraints. This is a slightly more aggressive * Check consistency of equality constraints. This is a slightly more aggressive
@ -647,6 +667,7 @@ public class Infer {
for (Type b : uv.getBounds(InferenceBound.UPPER)) { for (Type b : uv.getBounds(InferenceBound.UPPER)) {
if (inferenceContext.inferenceVars().contains(b)) { if (inferenceContext.inferenceVars().contains(b)) {
UndetVar uv2 = (UndetVar)inferenceContext.asFree(b); UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
if (uv2.isCaptured()) continue;
//alpha <: beta //alpha <: beta
//0. set beta :> alpha //0. set beta :> alpha
uv2.addBound(InferenceBound.LOWER, uv, infer.types); uv2.addBound(InferenceBound.LOWER, uv, infer.types);
@ -672,6 +693,7 @@ public class Infer {
for (Type b : uv.getBounds(InferenceBound.LOWER)) { for (Type b : uv.getBounds(InferenceBound.LOWER)) {
if (inferenceContext.inferenceVars().contains(b)) { if (inferenceContext.inferenceVars().contains(b)) {
UndetVar uv2 = (UndetVar)inferenceContext.asFree(b); UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
if (uv2.isCaptured()) continue;
//alpha :> beta //alpha :> beta
//0. set beta <: alpha //0. set beta <: alpha
uv2.addBound(InferenceBound.UPPER, uv, infer.types); uv2.addBound(InferenceBound.UPPER, uv, infer.types);
@ -697,6 +719,7 @@ public class Infer {
for (Type b : uv.getBounds(InferenceBound.EQ)) { for (Type b : uv.getBounds(InferenceBound.EQ)) {
if (inferenceContext.inferenceVars().contains(b)) { if (inferenceContext.inferenceVars().contains(b)) {
UndetVar uv2 = (UndetVar)inferenceContext.asFree(b); UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
if (uv2.isCaptured()) continue;
//alpha == beta //alpha == beta
//0. set beta == alpha //0. set beta == alpha
uv2.addBound(InferenceBound.EQ, uv, infer.types); uv2.addBound(InferenceBound.EQ, uv, infer.types);
@ -722,6 +745,10 @@ public class Infer {
}; };
abstract void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn); abstract void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn);
boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
return !uv.isCaptured();
}
} }
/** incorporation steps to be executed when running in legacy mode */ /** incorporation steps to be executed when running in legacy mode */
@ -1027,13 +1054,36 @@ public class Infer {
UPPER_LEGACY(InferenceBound.UPPER) { UPPER_LEGACY(InferenceBound.UPPER) {
@Override @Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) { public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return !inferenceContext.free(t.getBounds(ib)); return !inferenceContext.free(t.getBounds(ib)) && !t.isCaptured();
} }
@Override @Override
Type solve(UndetVar uv, InferenceContext inferenceContext) { Type solve(UndetVar uv, InferenceContext inferenceContext) {
return UPPER.solve(uv, inferenceContext); return UPPER.solve(uv, inferenceContext);
} }
},
/**
* Like the former; the only difference is that this step can only be applied
* if all upper/lower bounds are ground.
*/
CAPTURED(InferenceBound.UPPER) {
@Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return !inferenceContext.free(t.getBounds(InferenceBound.UPPER, InferenceBound.LOWER));
}
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
Infer infer = inferenceContext.infer();
Type upper = UPPER.filterBounds(uv, inferenceContext).nonEmpty() ?
UPPER.solve(uv, inferenceContext) :
infer.syms.objectType;
Type lower = LOWER.filterBounds(uv, inferenceContext).nonEmpty() ?
LOWER.solve(uv, inferenceContext) :
infer.syms.botType;
CapturedType prevCaptured = (CapturedType)uv.qtype;
return new CapturedType(prevCaptured.tsym.name, prevCaptured.tsym.owner, upper, lower, prevCaptured.wildcard);
}
}; };
final InferenceBound ib; final InferenceBound ib;
@ -1052,7 +1102,7 @@ public class Infer {
* Can the inference variable be instantiated using this step? * Can the inference variable be instantiated using this step?
*/ */
public boolean accepts(UndetVar t, InferenceContext inferenceContext) { public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return filterBounds(t, inferenceContext).nonEmpty(); return filterBounds(t, inferenceContext).nonEmpty() && !t.isCaptured();
} }
/** /**
@ -1089,7 +1139,7 @@ public class Infer {
EQ(EnumSet.of(InferenceStep.EQ)), EQ(EnumSet.of(InferenceStep.EQ)),
EQ_LOWER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER)), EQ_LOWER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER)),
EQ_LOWER_THROWS_UPPER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER, InferenceStep.THROWS, InferenceStep.UPPER)); EQ_LOWER_THROWS_UPPER_CAPTURED(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER, InferenceStep.UPPER, InferenceStep.THROWS, InferenceStep.CAPTURED));
final EnumSet<InferenceStep> steps; final EnumSet<InferenceStep> steps;
@ -1350,11 +1400,25 @@ public class Infer {
Mapping fromTypeVarFun = new Mapping("fromTypeVarFunWithBounds") { Mapping fromTypeVarFun = new Mapping("fromTypeVarFunWithBounds") {
// mapping that turns inference variables into undet vars // mapping that turns inference variables into undet vars
public Type apply(Type t) { public Type apply(Type t) {
if (t.hasTag(TYPEVAR)) return new UndetVar((TypeVar)t, types); if (t.hasTag(TYPEVAR)) {
else return t.map(this); TypeVar tv = (TypeVar)t;
return tv.isCaptured() ?
new CapturedUndetVar((CapturedType)tv, types) :
new UndetVar(tv, types);
} else {
return t.map(this);
}
} }
}; };
/**
* add a new inference var to this inference context
*/
void addVar(TypeVar t) {
this.undetvars = this.undetvars.prepend(fromTypeVarFun.apply(t));
this.inferencevars = this.inferencevars.prepend(t);
}
/** /**
* returns the list of free variables (as type-variables) in this * returns the list of free variables (as type-variables) in this
* inference context * inference context

View file

@ -961,10 +961,23 @@ public class Resolve {
DeferredType dt = (DeferredType)found; DeferredType dt = (DeferredType)found;
return dt.check(this); return dt.check(this);
} else { } else {
return super.check(pos, chk.checkNonVoid(pos, types.capture(types.upperBound(found.baseType())))); return super.check(pos, chk.checkNonVoid(pos, types.capture(U(found.baseType()))));
} }
} }
/**
* javac has a long-standing 'simplification' (see 6391995):
* given an actual argument type, the method check is performed
* on its upper bound. This leads to inconsistencies when an
* argument type is checked against itself. For example, given
* a type-variable T, it is not true that {@code U(T) <: T},
* so we need to guard against that.
*/
private Type U(Type found) {
return found == pt ?
found : types.upperBound(found);
}
@Override @Override
protected MethodResultInfo dup(Type newPt) { protected MethodResultInfo dup(Type newPt) {
return new MethodResultInfo(newPt, checkContext); return new MethodResultInfo(newPt, checkContext);

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8012238
* @summary Nested method capture and inference
* @compile NestedCapture01.java
*/
class NestedCapture01 {
void test(String s) {
g(m(s.getClass()));
}
<F extends String> F m(Class<F> cf) {
return null;
}
<P extends String> P g(P vo) {
return null;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8012238
* @summary Nested method capture and inference
* @compile NestedCapture02.java
*/
class NestedCapture02<S,T> {
<S,T> NestedCapture02<S,T> create(NestedCapture02<? super S,?> first,
NestedCapture02<? super S, T> second) {
return null;
}
<U> NestedCapture02<S, ? extends U> cast(Class<U> target) { return null; }
<U> NestedCapture02<S, ? extends U> test(Class<U> target,
NestedCapture02<? super S, ?> first, NestedCapture02<? super S, T> second) {
return create(first, second.cast(target));
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8012238
* @summary Nested method capture and inference
* @compile NestedCapture03.java
*/
class NestedCapture03 {
<T extends String> T factory(Class<T> c) { return null; }
void test(Class<?> c) {
factory(c.asSubclass(String.class)).matches(null);
}
}