@Hint(displayName="#DN_expression2Return", description="#DESC_expression2Return", category="suggestions", hintKind=Hint.Kind.ACTION, minSourceVersion = "8") @Messages({ "DN_expression2Return=Convert Lambda Body to Use a Block", "DESC_expression2Return=Converts lambda bodies to use blocks rather than expressions", "ERR_expression2Return=", "FIX_expression2Return=Use block as the lambda's body" }) @TriggerPattern("($args$) -> $lambdaExpression") public static ErrorDescription expression2Return(HintContext ctx) { if (((LambdaExpressionTree) ctx.getPath().getLeaf()).getBodyKind() != BodyKind.EXPRESSION) { return null; } TypeMirror lambdaExpressionType = ctx.getInfo().getTrees().getTypeMirror(ctx.getVariables().get("$lambdaExpression")); String target = lambdaExpressionType == null || lambdaExpressionType.getKind() != TypeKind.VOID ? "($args$) -> { return $lambdaExpression; }" : "($args$) -> { $lambdaExpression; }"; return ErrorDescriptionFactory.forTree(ctx, ctx.getPath(), Bundle.ERR_expression2Return(), JavaFixUtilities.rewriteFix(ctx, Bundle.FIX_expression2Return(), ctx.getPath(), target)); }
@Override public void visitLambda(JCLambda tree) { print(cs.spaceWithinLambdaParens() && tree.params.nonEmpty() ? "( " : "("); boolean oldPrintingMethodParams = printingMethodParams; printingMethodParams = true; suppressVariableType = tree.paramKind == JCLambda.ParameterKind.IMPLICIT; wrapTrees(tree.params, cs.wrapLambdaParams(), cs.alignMultilineLambdaParams() ? out.col : out.leftMargin + cs.getContinuationIndentSize(), true); suppressVariableType = false; printingMethodParams = oldPrintingMethodParams; if (cs.spaceWithinLambdaParens() && tree.params.nonEmpty()) needSpace(); print(')'); print(cs.spaceAroundLambdaArrow() ? " ->" : "->"); if (tree.getBodyKind() == BodyKind.STATEMENT) { printBlock(tree.body, cs.getOtherBracePlacement(), cs.spaceAroundLambdaArrow()); } else { int rm = cs.getRightMargin(); switch(cs.wrapBinaryOps()) { case WRAP_IF_LONG: if (widthEstimator.estimateWidth(tree.body, rm - out.col) + out.col <= cs.getRightMargin()) { if(cs.spaceAroundLambdaArrow()) print(' '); break; } case WRAP_ALWAYS: newline(); toColExactly(out.leftMargin + cs.getContinuationIndentSize()); break; case WRAP_NEVER: if(cs.spaceAroundLambdaArrow()) print(' '); break; } printExpr(tree.body, TreeInfo.notExpression); } }
boolean isSimpleStringArg(JCExpression e) { switch (e.getTag()) { case LAMBDA: JCLambda lambda = (JCLambda)e; return (lambda.getBodyKind() == BodyKind.EXPRESSION) && isSimpleStringArg((JCExpression)lambda.body); default: Symbol argSym = TreeInfo.symbolFor(e); return (e.type.constValue() != null || (argSym != null && argSym.kind == Kinds.Kind.VAR)); } }
/** Check lambda against given target result */ private void checkLambdaCompatible(Type descriptor, ResultInfo resultInfo) { CheckContext checkContext = resultInfo.checkContext; ResultInfo bodyResultInfo = attr.lambdaBodyResult(speculativeTree, descriptor, resultInfo); for (JCReturn ret : returnExpressions()) { Type t = getReturnType(ret); if (speculativeTree.getBodyKind() == BodyKind.EXPRESSION || !t.hasTag(VOID)) { checkSpeculative(ret.expr, t, bodyResultInfo); } } attr.checkLambdaCompatible(speculativeTree, descriptor, checkContext); }
@Override public void visitLambda(JCLambda tree) { if (inLambda || tree.getBodyKind() == BodyKind.EXPRESSION) { return; } inLambda = true; try { super.visitLambda(tree); } finally { inLambda = false; } }
@Override public void visitLambda(JCLambda tree) { final Bits prevUninits = new Bits(uninits); final Bits prevInits = new Bits(inits); int returnadrPrev = returnadr; int nextadrPrev = nextadr; ListBuffer<AssignPendingExit> prevPending = pendingExits; try { returnadr = nextadr; pendingExits = new ListBuffer<>(); for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) { JCVariableDecl def = l.head; scan(def); inits.incl(def.sym.adr); uninits.excl(def.sym.adr); } if (tree.getBodyKind() == JCLambda.BodyKind.EXPRESSION) { scanExpr(tree.body); } else { scan(tree.body); } } finally { returnadr = returnadrPrev; uninits.assign(prevUninits); inits.assign(prevInits); pendingExits = prevPending; nextadr = nextadrPrev; } }
/** * Performs speculative attribution of a lambda body and returns the speculative lambda tree, * in the absence of a target-type. Since {@link Attr#visitLambda(JCLambda)} cannot type-check * lambda bodies w/o a suitable target-type, this routine 'unrolls' the lambda by turning it * into a regular block, speculatively type-checks the block and then puts back the pieces. */ JCLambda attribSpeculativeLambda(JCLambda that, Env<AttrContext> env, ResultInfo resultInfo) { ListBuffer<JCStatement> stats = new ListBuffer<>(); stats.addAll(that.params); if (that.getBodyKind() == JCLambda.BodyKind.EXPRESSION) { stats.add(make.Return((JCExpression)that.body)); } else { stats.add((JCBlock)that.body); } JCBlock lambdaBlock = make.Block(0, stats.toList()); Env<AttrContext> localEnv = attr.lambdaEnv(that, env); try { localEnv.info.returnResult = resultInfo; JCBlock speculativeTree = (JCBlock)attribSpeculative(lambdaBlock, localEnv, resultInfo); List<JCVariableDecl> args = speculativeTree.getStatements().stream() .filter(s -> s.hasTag(Tag.VARDEF)) .map(t -> (JCVariableDecl)t) .collect(List.collector()); JCTree lambdaBody = speculativeTree.getStatements().last(); if (lambdaBody.hasTag(Tag.RETURN)) { lambdaBody = ((JCReturn)lambdaBody).expr; } JCLambda speculativeLambda = make.Lambda(args, lambdaBody); attr.preFlow(speculativeLambda); flow.analyzeLambda(env, speculativeLambda, make, false); return speculativeLambda; } finally { localEnv.info.scope.leave(); } }
@Override public void visitLambda(JCLambda tree) { if (tree.getBodyKind() == BodyKind.STATEMENT) { JCExpression ident = make.at(tree).QualIdent(symtab.assertionErrorType.tsym); JCThrow throwTree = make.Throw(make.NewClass(null, List.nil(), ident, List.nil(), null)); tree.body = make.Block(0, List.of(throwTree)); } }
@Override public BodyKind getBodyKind() { return body.hasTag(BLOCK) ? BodyKind.STATEMENT : BodyKind.EXPRESSION; }
@Override public void visitLambda(JCLambda tree) { Check.CheckContext checkContext = resultInfo.checkContext; Type pt = resultInfo.pt; if (!inferenceContext.inferencevars.contains(pt)) { //must be a functional descriptor Type descriptorType = null; try { descriptorType = types.findDescriptorType(pt); } catch (Types.FunctionDescriptorLookupError ex) { checkContext.report(null, ex.getDiagnostic()); } if (descriptorType.getParameterTypes().length() != tree.params.length()) { checkContext.report(tree, diags.fragment("incompatible.arg.types.in.lambda")); } Type currentReturnType = descriptorType.getReturnType(); boolean returnTypeIsVoid = currentReturnType.hasTag(VOID); if (tree.getBodyKind() == BodyKind.EXPRESSION) { boolean isExpressionCompatible = !returnTypeIsVoid || TreeInfo.isExpressionStatement((JCExpression)tree.getBody()); if (!isExpressionCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment("incompatible.ret.type.in.lambda", diags.fragment("missing.ret.val", currentReturnType))); } } else { LambdaBodyStructChecker lambdaBodyChecker = new LambdaBodyStructChecker(); tree.body.accept(lambdaBodyChecker); boolean isVoidCompatible = lambdaBodyChecker.isVoidCompatible; if (returnTypeIsVoid) { if (!isVoidCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment("unexpected.ret.val")); } } else { boolean isValueCompatible = lambdaBodyChecker.isPotentiallyValueCompatible && !canLambdaBodyCompleteNormally(tree); if (!isValueCompatible && !isVoidCompatible) { log.error(tree.body.pos(), "lambda.body.neither.value.nor.void.compatible"); } if (!isValueCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment("incompatible.ret.type.in.lambda", diags.fragment("missing.ret.val", currentReturnType))); } } } } }
@Override public void visitLambda(JCLambda tree) { Check.CheckContext checkContext = resultInfo.checkContext; Type pt = resultInfo.pt; if (!inferenceContext.inferencevars.contains(pt)) { //must be a functional descriptor Type descriptorType = null; try { descriptorType = types.findDescriptorType(pt); } catch (Types.FunctionDescriptorLookupError ex) { checkContext.report(null, ex.getDiagnostic()); } if (descriptorType.getParameterTypes().length() != tree.params.length()) { checkContext.report(tree, diags.fragment(Fragments.IncompatibleArgTypesInLambda)); } Type currentReturnType = descriptorType.getReturnType(); boolean returnTypeIsVoid = currentReturnType.hasTag(VOID); if (tree.getBodyKind() == BodyKind.EXPRESSION) { boolean isExpressionCompatible = !returnTypeIsVoid || TreeInfo.isExpressionStatement((JCExpression)tree.getBody()); if (!isExpressionCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment(Fragments.IncompatibleRetTypeInLambda(Fragments.MissingRetVal(currentReturnType)))); } } else { LambdaBodyStructChecker lambdaBodyChecker = new LambdaBodyStructChecker(); tree.body.accept(lambdaBodyChecker); boolean isVoidCompatible = lambdaBodyChecker.isVoidCompatible; if (returnTypeIsVoid) { if (!isVoidCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment(Fragments.UnexpectedRetVal)); } } else { boolean isValueCompatible = lambdaBodyChecker.isPotentiallyValueCompatible && !canLambdaBodyCompleteNormally(tree); if (!isValueCompatible && !isVoidCompatible) { log.error(tree.body.pos(), Errors.LambdaBodyNeitherValueNorVoidCompatible); } if (!isValueCompatible) { resultInfo.checkContext.report(tree.pos(), diags.fragment(Fragments.IncompatibleRetTypeInLambda(Fragments.MissingRetVal(currentReturnType)))); } } } } }
private SuggestedFix getFix(Tree releaseStatement, Symbol wakelockSymbol, VisitorState state) { // Wrap the release call line in a try/catch(RuntimeException) block. String before = "\ntry {\n"; String after = "\n} catch (RuntimeException unused) {\n" + "// Ignore: already released by timeout.\n" + "// TODO: Log this exception.\n" + "}\n"; // Lambda expressions are special. If the release call is in a one-expression lambda, // only wrap body (not args) and convert to block lambda. if (releaseStatement.getKind() == Kind.LAMBDA_EXPRESSION) { LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) releaseStatement; if (enclosingLambda.getBodyKind() == BodyKind.EXPRESSION) { releaseStatement = enclosingLambda.getBody(); before = "{" + before; after = ";" + after + "}"; } } // Remove `if (wakelock.isHeld())` check. // TODO(epmjohnston): can avoid this if no isHeld check in class (check call map). IfTree enclosingIfHeld = findEnclosingNode(state.getPath(), IfTree.class); if (enclosingIfHeld != null) { ExpressionTree condition = ASTHelpers.stripParentheses(enclosingIfHeld.getCondition()); if (enclosingIfHeld.getElseStatement() == null && instanceMethod() .onExactClass(WAKELOCK_CLASS_NAME) .named("isHeld") .matches(condition, state) && wakelockSymbol.equals(getSymbol(getReceiver(condition)))) { String ifBody = state.getSourceForNode(enclosingIfHeld.getThenStatement()).trim(); // Remove leading and trailing `{}` ifBody = ifBody.startsWith("{") ? ifBody.substring(1) : ifBody; ifBody = ifBody.endsWith("}") ? ifBody.substring(0, ifBody.length() - 1) : ifBody; ifBody = ifBody.trim(); String releaseStatementSource = state.getSourceForNode(releaseStatement); return SuggestedFix.replace( enclosingIfHeld, ifBody.replace(releaseStatementSource, before + releaseStatementSource + after)); } } return SuggestedFix.builder() .prefixWith(releaseStatement, before) .postfixWith(releaseStatement, after) .build(); }