8005243: Restructure method check code to allow pluggable checkers

Add interface to perform a method check - to be implemented by helper classes

Reviewed-by: jjg
This commit is contained in:
Maurizio Cimadamore 2013-01-08 10:15:30 +01:00
parent 259f5d7cc8
commit 159b251085
2 changed files with 174 additions and 212 deletions

View file

@ -114,7 +114,7 @@ public class Infer {
} }
} }
private final InferenceException inferenceException; final InferenceException inferenceException;
/*************************************************************************** /***************************************************************************
* Mini/Maximization of UndetVars * Mini/Maximization of UndetVars
@ -271,15 +271,19 @@ public class Infer {
boolean allowBoxing, boolean allowBoxing,
boolean useVarargs, boolean useVarargs,
Resolve.MethodResolutionContext resolveContext, Resolve.MethodResolutionContext resolveContext,
Resolve.MethodCheck methodCheck,
Warner warn) throws InferenceException { Warner warn) throws InferenceException {
//-System.err.println("instantiateMethod(" + tvars + ", " + mt + ", " + argtypes + ")"); //DEBUG //-System.err.println("instantiateMethod(" + tvars + ", " + mt + ", " + argtypes + ")"); //DEBUG
final InferenceContext inferenceContext = new InferenceContext(tvars, this, true); final InferenceContext inferenceContext = new InferenceContext(tvars, this, true);
inferenceException.clear(); inferenceException.clear();
DeferredAttr.DeferredAttrContext deferredAttrContext =
resolveContext.deferredAttrContext(msym, inferenceContext);
try { try {
rs.checkRawArgumentsAcceptable(env, msym, resolveContext.attrMode(), inferenceContext, methodCheck.argumentsAcceptable(env, deferredAttrContext, argtypes, mt.getParameterTypes(), warn);
argtypes, mt.getParameterTypes(), allowBoxing, useVarargs, warn,
new InferenceCheckHandler(inferenceContext)); deferredAttrContext.complete();
// minimize as yet undetermined type variables // minimize as yet undetermined type variables
for (Type t : inferenceContext.undetvars) { for (Type t : inferenceContext.undetvars) {
@ -309,32 +313,6 @@ public class Infer {
inferenceContext.notifyChange(types); inferenceContext.notifyChange(types);
} }
} }
//where
/** inference check handler **/
class InferenceCheckHandler implements Resolve.MethodCheckHandler {
InferenceContext inferenceContext;
public InferenceCheckHandler(InferenceContext inferenceContext) {
this.inferenceContext = inferenceContext;
}
public InapplicableMethodException arityMismatch() {
return inferenceException.setMessage("infer.arg.length.mismatch", inferenceContext.inferenceVars());
}
public InapplicableMethodException argumentMismatch(boolean varargs, JCDiagnostic details) {
String key = varargs ?
"infer.varargs.argument.mismatch" :
"infer.no.conforming.assignment.exists";
return inferenceException.setMessage(key,
inferenceContext.inferenceVars(), details);
}
public InapplicableMethodException inaccessibleVarargs(Symbol location, Type expected) {
return inferenceException.setMessage("inaccessible.varargs.type",
expected, Kinds.kindName(location), location);
}
}
/** check that type parameters are within their bounds. /** check that type parameters are within their bounds.
*/ */

View file

@ -506,6 +506,7 @@ public class Resolve {
List<Type> typeargtypes, List<Type> typeargtypes,
boolean allowBoxing, boolean allowBoxing,
boolean useVarargs, boolean useVarargs,
MethodCheck methodCheck,
Warner warn) throws Infer.InferenceException { Warner warn) throws Infer.InferenceException {
Type mt = types.memberType(site, m); Type mt = types.memberType(site, m);
@ -558,10 +559,11 @@ public class Resolve {
allowBoxing, allowBoxing,
useVarargs, useVarargs,
currentResolutionContext, currentResolutionContext,
methodCheck,
warn); warn);
checkRawArgumentsAcceptable(env, m, argtypes, mt.getParameterTypes(), methodCheck.argumentsAcceptable(env, currentResolutionContext.deferredAttrContext(m, infer.emptyContext),
allowBoxing, useVarargs, warn); argtypes, mt.getParameterTypes(), warn);
return mt; return mt;
} }
@ -578,7 +580,7 @@ public class Resolve {
currentResolutionContext.attrMode = DeferredAttr.AttrMode.CHECK; currentResolutionContext.attrMode = DeferredAttr.AttrMode.CHECK;
MethodResolutionPhase step = currentResolutionContext.step = env.info.pendingResolutionPhase; MethodResolutionPhase step = currentResolutionContext.step = env.info.pendingResolutionPhase;
return rawInstantiate(env, site, m, resultInfo, argtypes, typeargtypes, return rawInstantiate(env, site, m, resultInfo, argtypes, typeargtypes,
step.isBoxingRequired(), step.isVarargsRequired(), warn); step.isBoxingRequired(), step.isVarargsRequired(), resolveMethodCheck, warn);
} }
finally { finally {
currentResolutionContext = prevContext; currentResolutionContext = prevContext;
@ -595,80 +597,65 @@ public class Resolve {
List<Type> typeargtypes, List<Type> typeargtypes,
boolean allowBoxing, boolean allowBoxing,
boolean useVarargs, boolean useVarargs,
MethodCheck methodCheck,
Warner warn) { Warner warn) {
try { try {
return rawInstantiate(env, site, m, resultInfo, argtypes, typeargtypes, return rawInstantiate(env, site, m, resultInfo, argtypes, typeargtypes,
allowBoxing, useVarargs, warn); allowBoxing, useVarargs, methodCheck, warn);
} catch (InapplicableMethodException ex) { } catch (InapplicableMethodException ex) {
return null; return null;
} }
} }
/** Check if a parameter list accepts a list of args. /**
* This interface defines an entry point that should be used to perform a
* method check. A method check usually consist in determining as to whether
* a set of types (actuals) is compatible with another set of types (formals).
* Since the notion of compatibility can vary depending on the circumstances,
* this interfaces allows to easily add new pluggable method check routines.
*/ */
boolean argumentsAcceptable(Env<AttrContext> env, interface MethodCheck {
Symbol msym, /**
* Main method check routine. A method check usually consist in determining
* as to whether a set of types (actuals) is compatible with another set of
* types (formals). If an incompatibility is found, an unchecked exception
* is assumed to be thrown.
*/
void argumentsAcceptable(Env<AttrContext> env,
DeferredAttrContext deferredAttrContext,
List<Type> argtypes, List<Type> argtypes,
List<Type> formals, List<Type> formals,
boolean allowBoxing, Warner warn);
boolean useVarargs,
Warner warn) {
try {
checkRawArgumentsAcceptable(env, msym, argtypes, formals, allowBoxing, useVarargs, warn);
return true;
} catch (InapplicableMethodException ex) {
return false;
}
}
/**
* A check handler is used by the main method applicability routine in order
* to handle specific method applicability failures. It is assumed that a class
* implementing this interface should throw exceptions that are a subtype of
* InapplicableMethodException (see below). Such exception will terminate the
* method applicability check and propagate important info outwards (for the
* purpose of generating better diagnostics).
*/
interface MethodCheckHandler {
/* The number of actuals and formals differ */
InapplicableMethodException arityMismatch();
/* An actual argument type does not conform to the corresponding formal type */
InapplicableMethodException argumentMismatch(boolean varargs, JCDiagnostic details);
/* The element type of a varargs is not accessible in the current context */
InapplicableMethodException inaccessibleVarargs(Symbol location, Type expected);
} }
/** /**
* Basic method check handler used within Resolve - all methods end up * Helper enum defining all method check diagnostics (used by resolveMethodCheck).
* throwing InapplicableMethodException; a diagnostic fragment that describes
* the cause as to why the method is not applicable is set on the exception
* before it is thrown.
*/ */
MethodCheckHandler resolveHandler = new MethodCheckHandler() { enum MethodCheckDiag {
public InapplicableMethodException arityMismatch() { /**
return inapplicableMethodException.setMessage("arg.length.mismatch"); * Actuals and formals differs in length.
} */
public InapplicableMethodException argumentMismatch(boolean varargs, JCDiagnostic details) { ARITY_MISMATCH("arg.length.mismatch", "infer.arg.length.mismatch"),
String key = varargs ? /**
"varargs.argument.mismatch" : * An actual is incompatible with a formal.
"no.conforming.assignment.exists"; */
return inapplicableMethodException.setMessage(key, ARG_MISMATCH("no.conforming.assignment.exists", "infer.no.conforming.assignment.exists"),
details); /**
} * An actual is incompatible with the varargs element type.
public InapplicableMethodException inaccessibleVarargs(Symbol location, Type expected) { */
return inapplicableMethodException.setMessage("inaccessible.varargs.type", VARARG_MISMATCH("varargs.argument.mismatch", "infer.varargs.argument.mismatch"),
expected, Kinds.kindName(location), location); /**
} * The varargs element type is inaccessible.
}; */
INACCESSIBLE_VARARGS("inaccessible.varargs.type", "inaccessible.varargs.type");
void checkRawArgumentsAcceptable(Env<AttrContext> env, final String basicKey;
Symbol msym, final String inferKey;
List<Type> argtypes,
List<Type> formals, MethodCheckDiag(String basicKey, String inferKey) {
boolean allowBoxing, this.basicKey = basicKey;
boolean useVarargs, this.inferKey = inferKey;
Warner warn) { }
checkRawArgumentsAcceptable(env, msym, currentResolutionContext.attrMode(), infer.emptyContext, argtypes, formals,
allowBoxing, useVarargs, warn, resolveHandler);
} }
/** /**
@ -689,92 +676,119 @@ public class Resolve {
* *
* A method check handler (see above) is used in order to report errors. * A method check handler (see above) is used in order to report errors.
*/ */
void checkRawArgumentsAcceptable(final Env<AttrContext> env, MethodCheck resolveMethodCheck = new MethodCheck() {
Symbol msym, @Override
DeferredAttr.AttrMode mode, public void argumentsAcceptable(final Env<AttrContext> env,
final Infer.InferenceContext inferenceContext, DeferredAttrContext deferredAttrContext,
List<Type> argtypes, List<Type> argtypes,
List<Type> formals, List<Type> formals,
boolean allowBoxing, Warner warn) {
boolean useVarargs, //should we expand formals?
Warner warn, boolean useVarargs = deferredAttrContext.phase.isVarargsRequired();
final MethodCheckHandler handler) {
//inference context used during this method check
InferenceContext inferenceContext = deferredAttrContext.inferenceContext;
Type varargsFormal = useVarargs ? formals.last() : null; Type varargsFormal = useVarargs ? formals.last() : null;
if (varargsFormal == null && if (varargsFormal == null &&
argtypes.size() != formals.size()) { argtypes.size() != formals.size()) {
throw handler.arityMismatch(); // not enough args report(MethodCheckDiag.ARITY_MISMATCH, inferenceContext); // not enough args
} }
DeferredAttr.DeferredAttrContext deferredAttrContext =
deferredAttr.new DeferredAttrContext(mode, msym, currentResolutionContext.step, inferenceContext);
while (argtypes.nonEmpty() && formals.head != varargsFormal) { while (argtypes.nonEmpty() && formals.head != varargsFormal) {
ResultInfo mresult = methodCheckResult(formals.head, allowBoxing, false, inferenceContext, deferredAttrContext, handler, warn); ResultInfo mresult = methodCheckResult(false, formals.head, deferredAttrContext, warn);
mresult.check(null, argtypes.head); mresult.check(null, argtypes.head);
argtypes = argtypes.tail; argtypes = argtypes.tail;
formals = formals.tail; formals = formals.tail;
} }
if (formals.head != varargsFormal) { if (formals.head != varargsFormal) {
throw handler.arityMismatch(); // not enough args report(MethodCheckDiag.ARITY_MISMATCH, inferenceContext); // not enough args
} }
if (useVarargs) { if (useVarargs) {
//note: if applicability check is triggered by most specific test, //note: if applicability check is triggered by most specific test,
//the last argument of a varargs is _not_ an array type (see JLS 15.12.2.5) //the last argument of a varargs is _not_ an array type (see JLS 15.12.2.5)
final Type elt = types.elemtype(varargsFormal); final Type elt = types.elemtype(varargsFormal);
ResultInfo mresult = methodCheckResult(elt, allowBoxing, true, inferenceContext, deferredAttrContext, handler, warn); ResultInfo mresult = methodCheckResult(true, elt, deferredAttrContext, warn);
while (argtypes.nonEmpty()) { while (argtypes.nonEmpty()) {
mresult.check(null, argtypes.head); mresult.check(null, argtypes.head);
argtypes = argtypes.tail; argtypes = argtypes.tail;
} }
//check varargs element type accessibility //check varargs element type accessibility
varargsAccessible(env, elt, handler, inferenceContext); varargsAccessible(env, elt, inferenceContext);
}
} }
deferredAttrContext.complete(); private void report(MethodCheckDiag diag, InferenceContext inferenceContext, Object... args) {
boolean inferDiag = inferenceContext != infer.emptyContext;
InapplicableMethodException ex = inferDiag ?
infer.inferenceException : inapplicableMethodException;
if (inferDiag && (!diag.inferKey.equals(diag.basicKey))) {
Object[] args2 = new Object[args.length + 1];
System.arraycopy(args, 0, args2, 1, args.length);
args2[0] = inferenceContext.inferenceVars();
args = args2;
}
throw ex.setMessage(inferDiag ? diag.inferKey : diag.basicKey, args);
} }
void varargsAccessible(final Env<AttrContext> env, final Type t, final Resolve.MethodCheckHandler handler, final InferenceContext inferenceContext) { private void varargsAccessible(final Env<AttrContext> env, final Type t, final InferenceContext inferenceContext) {
if (inferenceContext.free(t)) { if (inferenceContext.free(t)) {
inferenceContext.addFreeTypeListener(List.of(t), new FreeTypeListener() { inferenceContext.addFreeTypeListener(List.of(t), new FreeTypeListener() {
@Override @Override
public void typesInferred(InferenceContext inferenceContext) { public void typesInferred(InferenceContext inferenceContext) {
varargsAccessible(env, inferenceContext.asInstType(t, types), handler, inferenceContext); varargsAccessible(env, inferenceContext.asInstType(t, types), inferenceContext);
} }
}); });
} else { } else {
if (!isAccessible(env, t)) { if (!isAccessible(env, t)) {
Symbol location = env.enclClass.sym; Symbol location = env.enclClass.sym;
throw handler.inaccessibleVarargs(location, t); report(MethodCheckDiag.INACCESSIBLE_VARARGS, inferenceContext, t, Kinds.kindName(location), location);
} }
} }
} }
private ResultInfo methodCheckResult(final boolean varargsCheck, Type to,
final DeferredAttr.DeferredAttrContext deferredAttrContext, Warner rsWarner) {
CheckContext checkContext = new MethodCheckContext(!deferredAttrContext.phase.isBoxingRequired(), deferredAttrContext, rsWarner) {
MethodCheckDiag methodDiag = varargsCheck ?
MethodCheckDiag.VARARG_MISMATCH : MethodCheckDiag.ARG_MISMATCH;
@Override
public void report(DiagnosticPosition pos, JCDiagnostic details) {
report(methodDiag, deferredAttrContext.inferenceContext, details);
}
};
return new MethodResultInfo(to, checkContext);
}
};
/** /**
* Check context to be used during method applicability checks. A method check * Check context to be used during method applicability checks. A method check
* context might contain inference variables. * context might contain inference variables.
*/ */
abstract class MethodCheckContext implements CheckContext { abstract class MethodCheckContext implements CheckContext {
MethodCheckHandler handler; boolean strict;
boolean useVarargs;
Infer.InferenceContext inferenceContext;
DeferredAttrContext deferredAttrContext; DeferredAttrContext deferredAttrContext;
Warner rsWarner; Warner rsWarner;
public MethodCheckContext(MethodCheckHandler handler, boolean useVarargs, public MethodCheckContext(boolean strict, DeferredAttrContext deferredAttrContext, Warner rsWarner) {
Infer.InferenceContext inferenceContext, DeferredAttrContext deferredAttrContext, Warner rsWarner) { this.strict = strict;
this.handler = handler;
this.useVarargs = useVarargs;
this.inferenceContext = inferenceContext;
this.deferredAttrContext = deferredAttrContext; this.deferredAttrContext = deferredAttrContext;
this.rsWarner = rsWarner; this.rsWarner = rsWarner;
} }
public boolean compatible(Type found, Type req, Warner warn) {
return strict ?
types.isSubtypeUnchecked(found, deferredAttrContext.inferenceContext.asFree(req, types), warn) :
types.isConvertible(found, deferredAttrContext.inferenceContext.asFree(req, types), warn);
}
public void report(DiagnosticPosition pos, JCDiagnostic details) { public void report(DiagnosticPosition pos, JCDiagnostic details) {
throw handler.argumentMismatch(useVarargs, details); throw inapplicableMethodException.setMessage(details);
} }
public Warner checkWarner(DiagnosticPosition pos, Type found, Type req) { public Warner checkWarner(DiagnosticPosition pos, Type found, Type req) {
@ -782,7 +796,7 @@ public class Resolve {
} }
public InferenceContext inferenceContext() { public InferenceContext inferenceContext() {
return inferenceContext; return deferredAttrContext.inferenceContext;
} }
public DeferredAttrContext deferredAttrContext() { public DeferredAttrContext deferredAttrContext() {
@ -791,56 +805,13 @@ public class Resolve {
} }
/** /**
* Subclass of method check context class that implements strict method conversion. * ResultInfo class to be used during method applicability checks. Check
* Strict method conversion checks compatibility between types using subtyping tests. * for deferred types goes through special path.
*/ */
class StrictMethodContext extends MethodCheckContext {
public StrictMethodContext(MethodCheckHandler handler, boolean useVarargs,
Infer.InferenceContext inferenceContext, DeferredAttrContext deferredAttrContext, Warner rsWarner) {
super(handler, useVarargs, inferenceContext, deferredAttrContext, rsWarner);
}
public boolean compatible(Type found, Type req, Warner warn) {
return types.isSubtypeUnchecked(found, inferenceContext.asFree(req, types), warn);
}
}
/**
* Subclass of method check context class that implements loose method conversion.
* Loose method conversion checks compatibility between types using method conversion tests.
*/
class LooseMethodContext extends MethodCheckContext {
public LooseMethodContext(MethodCheckHandler handler, boolean useVarargs,
Infer.InferenceContext inferenceContext, DeferredAttrContext deferredAttrContext, Warner rsWarner) {
super(handler, useVarargs, inferenceContext, deferredAttrContext, rsWarner);
}
public boolean compatible(Type found, Type req, Warner warn) {
return types.isConvertible(found, inferenceContext.asFree(req, types), warn);
}
}
/**
* Create a method check context to be used during method applicability check
*/
ResultInfo methodCheckResult(Type to, boolean allowBoxing, boolean useVarargs,
Infer.InferenceContext inferenceContext, DeferredAttr.DeferredAttrContext deferredAttrContext,
MethodCheckHandler methodHandler, Warner rsWarner) {
MethodCheckContext checkContext = allowBoxing ?
new LooseMethodContext(methodHandler, useVarargs, inferenceContext, deferredAttrContext, rsWarner) :
new StrictMethodContext(methodHandler, useVarargs, inferenceContext, deferredAttrContext, rsWarner);
return new MethodResultInfo(to, checkContext, deferredAttrContext);
}
class MethodResultInfo extends ResultInfo { class MethodResultInfo extends ResultInfo {
DeferredAttr.DeferredAttrContext deferredAttrContext; public MethodResultInfo(Type pt, CheckContext checkContext) {
public MethodResultInfo(Type pt, CheckContext checkContext, DeferredAttr.DeferredAttrContext deferredAttrContext) {
attr.super(VAL, pt, checkContext); attr.super(VAL, pt, checkContext);
this.deferredAttrContext = deferredAttrContext;
} }
@Override @Override
@ -855,12 +826,12 @@ public class Resolve {
@Override @Override
protected MethodResultInfo dup(Type newPt) { protected MethodResultInfo dup(Type newPt) {
return new MethodResultInfo(newPt, checkContext, deferredAttrContext); return new MethodResultInfo(newPt, checkContext);
} }
@Override @Override
protected ResultInfo dup(CheckContext newContext) { protected ResultInfo dup(CheckContext newContext) {
return new MethodResultInfo(pt, newContext, deferredAttrContext); return new MethodResultInfo(pt, newContext);
} }
} }
@ -1071,7 +1042,7 @@ public class Resolve {
Assert.check(sym.kind < AMBIGUOUS); Assert.check(sym.kind < AMBIGUOUS);
try { try {
Type mt = rawInstantiate(env, site, sym, null, argtypes, typeargtypes, Type mt = rawInstantiate(env, site, sym, null, argtypes, typeargtypes,
allowBoxing, useVarargs, types.noWarnings); allowBoxing, useVarargs, resolveMethodCheck, types.noWarnings);
if (!operator) if (!operator)
currentResolutionContext.addApplicableCandidate(sym, mt); currentResolutionContext.addApplicableCandidate(sym, mt);
} catch (InapplicableMethodException ex) { } catch (InapplicableMethodException ex) {
@ -1274,12 +1245,19 @@ public class Resolve {
} }
//where //where
private boolean invocationMoreSpecific(Env<AttrContext> env, Type site, Symbol m2, List<Type> argtypes1, boolean allowBoxing, boolean useVarargs) { private boolean invocationMoreSpecific(Env<AttrContext> env, Type site, Symbol m2, List<Type> argtypes1, boolean allowBoxing, boolean useVarargs) {
MethodResolutionContext prevContext = currentResolutionContext;
try {
currentResolutionContext = new MethodResolutionContext();
currentResolutionContext.step = allowBoxing ? BOX : BASIC;
noteWarner.clear(); noteWarner.clear();
Type mst = instantiate(env, site, m2, null, Type mst = instantiate(env, site, m2, null,
types.lowerBounds(argtypes1), null, types.lowerBounds(argtypes1), null,
allowBoxing, false, noteWarner); allowBoxing, false, resolveMethodCheck, noteWarner);
return mst != null && return mst != null &&
!noteWarner.hasLint(Lint.LintCategory.UNCHECKED); !noteWarner.hasLint(Lint.LintCategory.UNCHECKED);
} finally {
currentResolutionContext = prevContext;
}
} }
//where //where
private Symbol adjustVarargs(Symbol to, Symbol from, boolean useVarargs) { private Symbol adjustVarargs(Symbol to, Symbol from, boolean useVarargs) {
@ -2366,9 +2344,11 @@ public class Resolve {
try { try {
currentResolutionContext = new MethodResolutionContext(); currentResolutionContext = new MethodResolutionContext();
Name name = treeinfo.operatorName(optag); Name name = treeinfo.operatorName(optag);
env.info.pendingResolutionPhase = currentResolutionContext.step = BASIC;
Symbol sym = findMethod(env, syms.predefClass.type, name, argtypes, Symbol sym = findMethod(env, syms.predefClass.type, name, argtypes,
null, false, false, true); null, false, false, true);
if (boxingEnabled && sym.kind >= WRONG_MTHS) if (boxingEnabled && sym.kind >= WRONG_MTHS)
env.info.pendingResolutionPhase = currentResolutionContext.step = BOX;
sym = findMethod(env, syms.predefClass.type, name, argtypes, sym = findMethod(env, syms.predefClass.type, name, argtypes,
null, true, false, true); null, true, false, true);
return accessMethod(sym, pos, env.enclClass.sym.type, name, return accessMethod(sym, pos, env.enclClass.sym.type, name,
@ -3450,6 +3430,10 @@ public class Resolve {
candidates = candidates.append(c); candidates = candidates.append(c);
} }
DeferredAttrContext deferredAttrContext(Symbol sym, InferenceContext inferenceContext) {
return deferredAttr.new DeferredAttrContext(attrMode, sym, step, inferenceContext);
}
/** /**
* This class represents an overload resolution candidate. There are two * This class represents an overload resolution candidate. There are two
* kinds of candidates: applicable methods and inapplicable methods; * kinds of candidates: applicable methods and inapplicable methods;