1.1 --- a/src/share/classes/com/sun/tools/javac/comp/Attr.java Thu Oct 04 13:04:53 2012 +0100 1.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java Fri Oct 05 14:35:24 2012 +0100 1.3 @@ -43,6 +43,7 @@ 1.4 import com.sun.tools.javac.comp.Check.CheckContext; 1.5 1.6 import com.sun.source.tree.IdentifierTree; 1.7 +import com.sun.source.tree.LambdaExpressionTree; 1.8 import com.sun.source.tree.MemberSelectTree; 1.9 import com.sun.source.tree.TreeVisitor; 1.10 import com.sun.source.util.SimpleTreeVisitor; 1.11 @@ -85,6 +86,7 @@ 1.12 final Infer infer; 1.13 final DeferredAttr deferredAttr; 1.14 final Check chk; 1.15 + final Flow flow; 1.16 final MemberEnter memberEnter; 1.17 final TreeMaker make; 1.18 final ConstFold cfolder; 1.19 @@ -110,6 +112,7 @@ 1.20 syms = Symtab.instance(context); 1.21 rs = Resolve.instance(context); 1.22 chk = Check.instance(context); 1.23 + flow = Flow.instance(context); 1.24 memberEnter = MemberEnter.instance(context); 1.25 make = TreeMaker.instance(context); 1.26 enter = Enter.instance(context); 1.27 @@ -133,17 +136,20 @@ 1.28 allowAnonOuterThis = source.allowAnonOuterThis(); 1.29 allowStringsInSwitch = source.allowStringsInSwitch(); 1.30 allowPoly = source.allowPoly() && options.isSet("allowPoly"); 1.31 + allowLambda = source.allowLambda(); 1.32 sourceName = source.name; 1.33 relax = (options.isSet("-retrofit") || 1.34 options.isSet("-relax")); 1.35 findDiamonds = options.get("findDiamond") != null && 1.36 source.allowDiamond(); 1.37 useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); 1.38 + identifyLambdaCandidate = options.getBoolean("identifyLambdaCandidate", false); 1.39 1.40 statInfo = new ResultInfo(NIL, Type.noType); 1.41 varInfo = new ResultInfo(VAR, Type.noType); 1.42 unknownExprInfo = new ResultInfo(VAL, Type.noType); 1.43 unknownTypeInfo = new ResultInfo(TYP, Type.noType); 1.44 + recoveryInfo = new RecoveryInfo(deferredAttr.emptyDeferredAttrContext); 1.45 } 1.46 1.47 /** Switch: relax some constraints for retrofit mode. 1.48 @@ -174,6 +180,10 @@ 1.49 */ 1.50 boolean allowCovariantReturns; 1.51 1.52 + /** Switch: support lambda expressions ? 1.53 + */ 1.54 + boolean allowLambda; 1.55 + 1.56 /** Switch: allow references to surrounding object from anonymous 1.57 * objects during constructor call? 1.58 */ 1.59 @@ -196,6 +206,12 @@ 1.60 boolean useBeforeDeclarationWarning; 1.61 1.62 /** 1.63 + * Switch: generate warnings whenever an anonymous inner class that is convertible 1.64 + * to a lambda expression is found 1.65 + */ 1.66 + boolean identifyLambdaCandidate; 1.67 + 1.68 + /** 1.69 * Switch: allow strings in switch? 1.70 */ 1.71 boolean allowStringsInSwitch; 1.72 @@ -286,6 +302,9 @@ 1.73 case CLASSDEF: 1.74 //class def is always an owner 1.75 return ((JCClassDecl)env.tree).sym; 1.76 + case LAMBDA: 1.77 + //a lambda is an owner - return a fresh synthetic method symbol 1.78 + return new MethodSymbol(0, names.empty, null, syms.methodClass); 1.79 case BLOCK: 1.80 //static/instance init blocks are owner 1.81 Symbol blockSym = env.info.scope.owner; 1.82 @@ -505,10 +524,36 @@ 1.83 } 1.84 } 1.85 1.86 + class RecoveryInfo extends ResultInfo { 1.87 + 1.88 + public RecoveryInfo(final DeferredAttr.DeferredAttrContext deferredAttrContext) { 1.89 + super(Kinds.VAL, Type.recoveryType, new Check.NestedCheckContext(chk.basicHandler) { 1.90 + @Override 1.91 + public DeferredAttr.DeferredAttrContext deferredAttrContext() { 1.92 + return deferredAttrContext; 1.93 + } 1.94 + @Override 1.95 + public boolean compatible(Type found, Type req, Warner warn) { 1.96 + return true; 1.97 + } 1.98 + @Override 1.99 + public void report(DiagnosticPosition pos, JCDiagnostic details) { 1.100 + //do nothing 1.101 + } 1.102 + }); 1.103 + } 1.104 + 1.105 + @Override 1.106 + protected Type check(DiagnosticPosition pos, Type found) { 1.107 + return chk.checkNonVoid(pos, super.check(pos, found)); 1.108 + } 1.109 + } 1.110 + 1.111 final ResultInfo statInfo; 1.112 final ResultInfo varInfo; 1.113 final ResultInfo unknownExprInfo; 1.114 final ResultInfo unknownTypeInfo; 1.115 + final ResultInfo recoveryInfo; 1.116 1.117 Type pt() { 1.118 return resultInfo.pt; 1.119 @@ -987,7 +1032,9 @@ 1.120 chk.checkDeprecatedAnnotation(tree.pos(), v); 1.121 1.122 if (tree.init != null) { 1.123 - if ((v.flags_field & FINAL) != 0 && !tree.init.hasTag(NEWCLASS)) { 1.124 + if ((v.flags_field & FINAL) != 0 && 1.125 + !tree.init.hasTag(NEWCLASS) && 1.126 + !tree.init.hasTag(LAMBDA)) { 1.127 // In this case, `v' is final. Ensure that it's initializer is 1.128 // evaluated. 1.129 v.getConstValue(); // ensure initializer is evaluated 1.130 @@ -1501,37 +1548,38 @@ 1.131 LOOP: 1.132 while (env1 != null) { 1.133 switch (env1.tree.getTag()) { 1.134 - case LABELLED: 1.135 - JCLabeledStatement labelled = (JCLabeledStatement)env1.tree; 1.136 - if (label == labelled.label) { 1.137 - // If jump is a continue, check that target is a loop. 1.138 - if (tag == CONTINUE) { 1.139 - if (!labelled.body.hasTag(DOLOOP) && 1.140 - !labelled.body.hasTag(WHILELOOP) && 1.141 - !labelled.body.hasTag(FORLOOP) && 1.142 - !labelled.body.hasTag(FOREACHLOOP)) 1.143 - log.error(pos, "not.loop.label", label); 1.144 - // Found labelled statement target, now go inwards 1.145 - // to next non-labelled tree. 1.146 - return TreeInfo.referencedStatement(labelled); 1.147 - } else { 1.148 - return labelled; 1.149 + case LABELLED: 1.150 + JCLabeledStatement labelled = (JCLabeledStatement)env1.tree; 1.151 + if (label == labelled.label) { 1.152 + // If jump is a continue, check that target is a loop. 1.153 + if (tag == CONTINUE) { 1.154 + if (!labelled.body.hasTag(DOLOOP) && 1.155 + !labelled.body.hasTag(WHILELOOP) && 1.156 + !labelled.body.hasTag(FORLOOP) && 1.157 + !labelled.body.hasTag(FOREACHLOOP)) 1.158 + log.error(pos, "not.loop.label", label); 1.159 + // Found labelled statement target, now go inwards 1.160 + // to next non-labelled tree. 1.161 + return TreeInfo.referencedStatement(labelled); 1.162 + } else { 1.163 + return labelled; 1.164 + } 1.165 } 1.166 - } 1.167 - break; 1.168 - case DOLOOP: 1.169 - case WHILELOOP: 1.170 - case FORLOOP: 1.171 - case FOREACHLOOP: 1.172 - if (label == null) return env1.tree; 1.173 - break; 1.174 - case SWITCH: 1.175 - if (label == null && tag == BREAK) return env1.tree; 1.176 - break; 1.177 - case METHODDEF: 1.178 - case CLASSDEF: 1.179 - break LOOP; 1.180 - default: 1.181 + break; 1.182 + case DOLOOP: 1.183 + case WHILELOOP: 1.184 + case FORLOOP: 1.185 + case FOREACHLOOP: 1.186 + if (label == null) return env1.tree; 1.187 + break; 1.188 + case SWITCH: 1.189 + if (label == null && tag == BREAK) return env1.tree; 1.190 + break; 1.191 + case LAMBDA: 1.192 + case METHODDEF: 1.193 + case CLASSDEF: 1.194 + break LOOP; 1.195 + default: 1.196 } 1.197 env1 = env1.next; 1.198 } 1.199 @@ -1961,6 +2009,8 @@ 1.200 1.201 attribStat(cdef, localEnv); 1.202 1.203 + checkLambdaCandidate(tree, cdef.sym, clazztype); 1.204 + 1.205 // If an outer instance is given, 1.206 // prefix it to the constructor arguments 1.207 // and delete it from the new expression 1.208 @@ -2016,6 +2066,32 @@ 1.209 } 1.210 } 1.211 1.212 + private void checkLambdaCandidate(JCNewClass tree, ClassSymbol csym, Type clazztype) { 1.213 + if (allowLambda && 1.214 + identifyLambdaCandidate && 1.215 + clazztype.tag == CLASS && 1.216 + pt().tag != NONE && 1.217 + types.isFunctionalInterface(clazztype.tsym)) { 1.218 + Symbol descriptor = types.findDescriptorSymbol(clazztype.tsym); 1.219 + int count = 0; 1.220 + boolean found = false; 1.221 + for (Symbol sym : csym.members().getElements()) { 1.222 + if ((sym.flags() & SYNTHETIC) != 0 || 1.223 + sym.isConstructor()) continue; 1.224 + count++; 1.225 + if (sym.kind != MTH || 1.226 + !sym.name.equals(descriptor.name)) continue; 1.227 + Type mtype = types.memberType(clazztype, sym); 1.228 + if (types.overrideEquivalent(mtype, types.memberType(clazztype, descriptor))) { 1.229 + found = true; 1.230 + } 1.231 + } 1.232 + if (found && count == 1) { 1.233 + log.note(tree.def, "potential.lambda.found"); 1.234 + } 1.235 + } 1.236 + } 1.237 + 1.238 /** Make an attributed null check tree. 1.239 */ 1.240 public JCExpression makeNullCheck(JCExpression arg) { 1.241 @@ -2064,15 +2140,222 @@ 1.242 result = check(tree, owntype, VAL, resultInfo); 1.243 } 1.244 1.245 + /* 1.246 + * A lambda expression can only be attributed when a target-type is available. 1.247 + * In addition, if the target-type is that of a functional interface whose 1.248 + * descriptor contains inference variables in argument position the lambda expression 1.249 + * is 'stuck' (see DeferredAttr). 1.250 + */ 1.251 @Override 1.252 - public void visitLambda(JCLambda that) { 1.253 - throw new UnsupportedOperationException("Lambda expression not supported yet"); 1.254 + public void visitLambda(final JCLambda that) { 1.255 + if (pt().isErroneous() || (pt().tag == NONE && pt() != Type.recoveryType)) { 1.256 + if (pt().tag == NONE) { 1.257 + //lambda only allowed in assignment or method invocation/cast context 1.258 + log.error(that.pos(), "unexpected.lambda"); 1.259 + } 1.260 + result = that.type = types.createErrorType(pt()); 1.261 + return; 1.262 + } 1.263 + //create an environment for attribution of the lambda expression 1.264 + final Env<AttrContext> localEnv = lambdaEnv(that, env); 1.265 + boolean needsRecovery = resultInfo.checkContext.deferredAttrContext() == deferredAttr.emptyDeferredAttrContext || 1.266 + resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.CHECK; 1.267 + try { 1.268 + List<Type> explicitParamTypes = null; 1.269 + if (TreeInfo.isExplicitLambda(that)) { 1.270 + //attribute lambda parameters 1.271 + attribStats(that.params, localEnv); 1.272 + explicitParamTypes = TreeInfo.types(that.params); 1.273 + } 1.274 + 1.275 + Type target = infer.instantiateFunctionalInterface(that, pt(), explicitParamTypes, resultInfo.checkContext); 1.276 + Type lambdaType = (target == Type.recoveryType) ? 1.277 + fallbackDescriptorType(that) : 1.278 + types.findDescriptorType(target); 1.279 + 1.280 + if (!TreeInfo.isExplicitLambda(that)) { 1.281 + //add param type info in the AST 1.282 + List<Type> actuals = lambdaType.getParameterTypes(); 1.283 + List<JCVariableDecl> params = that.params; 1.284 + 1.285 + boolean arityMismatch = false; 1.286 + 1.287 + while (params.nonEmpty()) { 1.288 + if (actuals.isEmpty()) { 1.289 + //not enough actuals to perform lambda parameter inference 1.290 + arityMismatch = true; 1.291 + } 1.292 + //reset previously set info 1.293 + Type argType = arityMismatch ? 1.294 + syms.errType : 1.295 + actuals.head; 1.296 + params.head.vartype = make.Type(argType); 1.297 + params.head.sym = null; 1.298 + actuals = actuals.isEmpty() ? 1.299 + actuals : 1.300 + actuals.tail; 1.301 + params = params.tail; 1.302 + } 1.303 + 1.304 + //attribute lambda parameters 1.305 + attribStats(that.params, localEnv); 1.306 + 1.307 + if (arityMismatch) { 1.308 + resultInfo.checkContext.report(that, diags.fragment("incompatible.arg.types.in.lambda")); 1.309 + result = that.type = types.createErrorType(target); 1.310 + return; 1.311 + } 1.312 + } 1.313 + 1.314 + //from this point on, no recovery is needed; if we are in assignment context 1.315 + //we will be able to attribute the whole lambda body, regardless of errors; 1.316 + //if we are in a 'check' method context, and the lambda is not compatible 1.317 + //with the target-type, it will be recovered anyway in Attr.checkId 1.318 + needsRecovery = false; 1.319 + 1.320 + ResultInfo bodyResultInfo = lambdaType.getReturnType() == Type.recoveryType ? 1.321 + recoveryInfo : 1.322 + new ResultInfo(VAL, lambdaType.getReturnType(), new LambdaReturnContext(resultInfo.checkContext)); 1.323 + localEnv.info.returnResult = bodyResultInfo; 1.324 + 1.325 + if (that.getBodyKind() == JCLambda.BodyKind.EXPRESSION) { 1.326 + attribTree(that.getBody(), localEnv, bodyResultInfo); 1.327 + } else { 1.328 + JCBlock body = (JCBlock)that.body; 1.329 + attribStats(body.stats, localEnv); 1.330 + } 1.331 + 1.332 + result = check(that, target, VAL, resultInfo); 1.333 + 1.334 + boolean isSpeculativeRound = 1.335 + resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.SPECULATIVE; 1.336 + 1.337 + postAttr(that); 1.338 + flow.analyzeLambda(env, that, make, isSpeculativeRound); 1.339 + 1.340 + checkLambdaCompatible(that, lambdaType, resultInfo.checkContext, isSpeculativeRound); 1.341 + 1.342 + if (!isSpeculativeRound) { 1.343 + checkAccessibleFunctionalDescriptor(that, localEnv, resultInfo.checkContext.inferenceContext(), lambdaType); 1.344 + } 1.345 + result = check(that, target, VAL, resultInfo); 1.346 + } catch (Types.FunctionDescriptorLookupError ex) { 1.347 + JCDiagnostic cause = ex.getDiagnostic(); 1.348 + resultInfo.checkContext.report(that, cause); 1.349 + result = that.type = types.createErrorType(pt()); 1.350 + return; 1.351 + } finally { 1.352 + localEnv.info.scope.leave(); 1.353 + if (needsRecovery) { 1.354 + attribTree(that, env, recoveryInfo); 1.355 + } 1.356 + } 1.357 } 1.358 - 1.359 - @Override 1.360 - public void visitReference(JCMemberReference that) { 1.361 - throw new UnsupportedOperationException("Member references not supported yet"); 1.362 - } 1.363 + //where 1.364 + private Type fallbackDescriptorType(JCExpression tree) { 1.365 + switch (tree.getTag()) { 1.366 + case LAMBDA: 1.367 + JCLambda lambda = (JCLambda)tree; 1.368 + List<Type> argtypes = List.nil(); 1.369 + for (JCVariableDecl param : lambda.params) { 1.370 + argtypes = param.vartype != null ? 1.371 + argtypes.append(param.vartype.type) : 1.372 + argtypes.append(syms.errType); 1.373 + } 1.374 + return new MethodType(argtypes, Type.recoveryType, List.<Type>nil(), syms.methodClass); 1.375 + case REFERENCE: 1.376 + return new MethodType(List.<Type>nil(), Type.recoveryType, List.<Type>nil(), syms.methodClass); 1.377 + default: 1.378 + Assert.error("Cannot get here!"); 1.379 + } 1.380 + return null; 1.381 + } 1.382 + 1.383 + private void checkAccessibleFunctionalDescriptor(final DiagnosticPosition pos, 1.384 + final Env<AttrContext> env, final InferenceContext inferenceContext, final Type desc) { 1.385 + if (inferenceContext.free(desc)) { 1.386 + inferenceContext.addFreeTypeListener(List.of(desc), new FreeTypeListener() { 1.387 + @Override 1.388 + public void typesInferred(InferenceContext inferenceContext) { 1.389 + checkAccessibleFunctionalDescriptor(pos, env, inferenceContext, inferenceContext.asInstType(desc, types)); 1.390 + } 1.391 + }); 1.392 + } else { 1.393 + chk.checkAccessibleFunctionalDescriptor(pos, env, desc); 1.394 + } 1.395 + } 1.396 + 1.397 + /** 1.398 + * Lambda/method reference have a special check context that ensures 1.399 + * that i.e. a lambda return type is compatible with the expected 1.400 + * type according to both the inherited context and the assignment 1.401 + * context. 1.402 + */ 1.403 + class LambdaReturnContext extends Check.NestedCheckContext { 1.404 + public LambdaReturnContext(CheckContext enclosingContext) { 1.405 + super(enclosingContext); 1.406 + } 1.407 + 1.408 + @Override 1.409 + public boolean compatible(Type found, Type req, Warner warn) { 1.410 + //return type must be compatible in both current context and assignment context 1.411 + return types.isAssignable(found, inferenceContext().asFree(req, types), warn) && 1.412 + super.compatible(found, req, warn); 1.413 + } 1.414 + @Override 1.415 + public void report(DiagnosticPosition pos, JCDiagnostic details) { 1.416 + enclosingContext.report(pos, diags.fragment("incompatible.ret.type.in.lambda", details)); 1.417 + } 1.418 + } 1.419 + 1.420 + /** 1.421 + * Lambda compatibility. Check that given return types, thrown types, parameter types 1.422 + * are compatible with the expected functional interface descriptor. This means that: 1.423 + * (i) parameter types must be identical to those of the target descriptor; (ii) return 1.424 + * types must be compatible with the return type of the expected descriptor; 1.425 + * (iii) thrown types must be 'included' in the thrown types list of the expected 1.426 + * descriptor. 1.427 + */ 1.428 + private void checkLambdaCompatible(JCLambda tree, Type descriptor, CheckContext checkContext, boolean speculativeAttr) { 1.429 + Type returnType = checkContext.inferenceContext().asFree(descriptor.getReturnType(), types); 1.430 + 1.431 + //return values have already been checked - but if lambda has no return 1.432 + //values, we must ensure that void/value compatibility is correct; 1.433 + //this amounts at checking that, if a lambda body can complete normally, 1.434 + //the descriptor's return type must be void 1.435 + if (tree.getBodyKind() == JCLambda.BodyKind.STATEMENT && tree.canCompleteNormally && 1.436 + returnType.tag != VOID && returnType != Type.recoveryType) { 1.437 + checkContext.report(tree, diags.fragment("incompatible.ret.type.in.lambda", 1.438 + diags.fragment("missing.ret.val", returnType))); 1.439 + } 1.440 + 1.441 + List<Type> argTypes = checkContext.inferenceContext().asFree(descriptor.getParameterTypes(), types); 1.442 + if (!types.isSameTypes(argTypes, TreeInfo.types(tree.params))) { 1.443 + checkContext.report(tree, diags.fragment("incompatible.arg.types.in.lambda")); 1.444 + } 1.445 + 1.446 + if (!speculativeAttr) { 1.447 + List<Type> thrownTypes = checkContext.inferenceContext().asFree(descriptor.getThrownTypes(), types); 1.448 + if (chk.unhandled(tree.inferredThrownTypes == null ? List.<Type>nil() : tree.inferredThrownTypes, thrownTypes).nonEmpty()) { 1.449 + log.error(tree, "incompatible.thrown.types.in.lambda", tree.inferredThrownTypes); 1.450 + } 1.451 + } 1.452 + } 1.453 + 1.454 + private Env<AttrContext> lambdaEnv(JCLambda that, Env<AttrContext> env) { 1.455 + Env<AttrContext> lambdaEnv; 1.456 + Symbol owner = env.info.scope.owner; 1.457 + if (owner.kind == VAR && owner.owner.kind == TYP) { 1.458 + //field initializer 1.459 + lambdaEnv = env.dup(that, env.info.dup(env.info.scope.dupUnshared())); 1.460 + lambdaEnv.info.scope.owner = 1.461 + new MethodSymbol(0, names.empty, null, 1.462 + env.info.scope.owner); 1.463 + } else { 1.464 + lambdaEnv = env.dup(that, env.info.dup(env.info.scope.dup())); 1.465 + } 1.466 + return lambdaEnv; 1.467 + } 1.468 1.469 public void visitParens(JCParens tree) { 1.470 Type owntype = attribTree(tree.expr, env, resultInfo); 1.471 @@ -3355,8 +3638,8 @@ 1.472 * mode (e.g. by an IDE) and the AST contains semantic errors, this routine 1.473 * prevents NPE to be progagated during subsequent compilation steps. 1.474 */ 1.475 - public void postAttr(Env<AttrContext> env) { 1.476 - new PostAttrAnalyzer().scan(env.tree); 1.477 + public void postAttr(JCTree tree) { 1.478 + new PostAttrAnalyzer().scan(tree); 1.479 } 1.480 1.481 class PostAttrAnalyzer extends TreeScanner {