changeset 660:c572fa185d05

Added improved support for inference-related diagnostic information. The compiler emits some explanatory warnings in case thrown types cannot be inferred from the body of a lambda expression; the message looks like: Inference.java:16: warning: thrown types cannot be inferred from lambda body because of cyclic inference il.forEach( #( arg ) { arg.m() } ); ^ explicit type required for the following parameter: arg These messages can be enabled/disabled using the compiler flag '-XDlambdaInferenceDiags=(true,false)' [default enabled].
author mcimadamore
date Mon, 06 Sep 2010 15:01:38 +0100
parents db1d811040a5
children 52681eb717e7
files src/share/classes/com/sun/tools/javac/comp/Attr.java src/share/classes/com/sun/tools/javac/comp/Flow.java src/share/classes/com/sun/tools/javac/resources/compiler.properties test/tools/javac/diags/examples.not-yet.txt test/tools/javac/lambda/LambdaExprNotVoid.java test/tools/javac/lambda/TargetType01.java test/tools/javac/lambda/TargetType13.java
diffstat 7 files changed, 91 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/tools/javac/comp/Attr.java	Thu Sep 02 18:30:13 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Sep 06 15:01:38 2010 +0100
@@ -123,6 +123,8 @@
         allowAnonOuterThis = source.allowAnonOuterThis();
         allowStringsInSwitch = source.allowStringsInSwitch();
         allowLambda = source.allowLambda();
+        lambdaInferenceDiags = options.get("lambdaInferenceDiags") == null ||
+                !options.get("lambdaInferenceDiags").equals("false");
         sourceName = source.name;
         relax = (options.get("-retrofit") != null ||
                  options.get("-relax") != null);
@@ -184,6 +186,11 @@
     boolean identifyLambdaCandidate;
 
     /**
+     * Switch: generate detailed warnings about lambda inference
+     */
+    boolean lambdaInferenceDiags;
+
+    /**
      * Switch: allow strings in switch?
      */
     boolean allowStringsInSwitch;
@@ -2161,7 +2168,7 @@
         for (JCVariableDecl param : tempLambda.params) {
             if (param.vartype == null) {
                 TypeSymbol tvSym = new TypeSymbol(LAMBDA,
-                        names.fromString("A_"+param.name),
+                        param.name,
                         null,
                         env.info.scope.owner);
                 TypeVar tv = new TypeVar(tvSym, Type.noType, null);
@@ -2174,17 +2181,7 @@
                 argTypes.append(attribType(param.vartype, env));
             }
         }
-        Type ft = null;
-        boolean prevLogEnabled = log.isEnabled();
-        try {
-            //disable the log -- we need to do this in order to avoid
-            //spurious error messages from this partial attribution sweep
-            log.setEnabled(false);
-            ft = attribLambda(tempLambda);
-        }
-        finally {
-            log.setEnabled(prevLogEnabled);
-        }
+        Type ft = attribLambda(tempLambda);
         return that.type = new ForAll(tvars.toList(), ft) {
             @Override
             public Type inst(List<Type> actuals, Type to, Types types) {
@@ -2217,29 +2214,38 @@
         final Env<AttrContext> localEnv = lambdaEnvironment(that,
                 new FunctionType(List.<Type>nil(), Type.noType, List.<Type>nil(), syms.methodHandleType.tsym));
 
-        //attribute lambda parameters
-        attribStats(that.params, localEnv);
+        Type resType = null;
         ListBuffer<Type> argtypes = ListBuffer.lb();
-        for (JCTree arg : that.params) {
-            argtypes.append(arg.type);
+
+        boolean prevLogEnabled = log.isEnabled();
+        try {
+            //disable the log -- we need to do this in order to avoid
+            //spurious error messages from this partial attribution sweep
+            log.setEnabled(false);
+            //attribute lambda parameters
+            attribStats(that.params, localEnv);
+            for (JCTree arg : that.params) {
+                argtypes.append(arg.type);
+            }
+
+            if (that.getBodyKind() == JCLambda.BodyKind.EXPRESSION) {
+                //the return type is the type of the expression
+                resType = attribExpr(that.getBody(), localEnv);
+            }
+            else {
+                //the return type is the union of all types
+                //returned by the lambda
+                JCBlock body = (JCBlock)that.body;
+                attribStats(body.stats, localEnv);
+                resType = localEnv.info.scope.owner.type.getReturnType();
+                if (resType == null) {
+                    //this happens if the body contains an expression statement
+                    resType = syms.voidType;
+                }
+            }
         }
-
-        Type resType = null;
-
-        if (that.getBodyKind() == JCLambda.BodyKind.EXPRESSION) {
-            //the return type is the type of the expression
-            resType = attribExpr(that.getBody(), localEnv);
-        }
-        else {
-            //the return type is the union of all types
-            //returned by the lambda
-            JCBlock body = (JCBlock)that.body;
-            attribStats(body.stats, localEnv);
-            resType = localEnv.info.scope.owner.type.getReturnType();
-            if (resType == null) {
-                //this happens if the body contains an expression statement
-                resType = syms.voidType;
-            }
+        finally {
+            log.setEnabled(prevLogEnabled);
         }
 
         //there are two causes that disable inference of lambda return type:
@@ -2264,6 +2270,16 @@
         //pass, then thrown types are left uninferred (as e.g. calling an unknown
         //method on an unknown type might raise unknown exception types).
         if (log.suppressedDiagnostics.containsKey(JCDiagnostic.Kind.ERROR)) {
+            ListBuffer<Name> uninferredTypeNames = ListBuffer.lb();
+            for (JCDiagnostic diag : log.suppressedDiagnostics.get(JCDiagnostic.Kind.ERROR)) {
+                searchUninferredTypes(diag, uninferredTypeNames);
+            }
+            if (uninferredTypeNames.nonEmpty() && lambdaInferenceDiags) {
+                String warnKey = uninferredTypeNames.size() == 1 ?
+                    "cyclic.lambda.inference.throws" :
+                    "cyclic.lambda.inference.throws.1";
+                log.warning(that.pos(), warnKey, uninferredTypeNames);
+            }
             thrownTypes = Type.noTypes;
         }
 
@@ -2279,6 +2295,34 @@
         return that.type;
     }
 
+    void searchUninferredTypes(JCDiagnostic suppressedDiag, ListBuffer<Name> buf) {
+        for (Object o : suppressedDiag.getArgs()) {
+            searchUninferredTypesInArg(o, buf);
+        }
+    }
+
+    void searchUninferredTypesInArg(Object o, ListBuffer<Name> buf) {
+        if (o instanceof Type) {
+            Type t = (Type)o;
+            if (t.tsym != null &&
+                    (t.tsym.flags() & LAMBDA) != 0 &&
+                    !buf.contains(t.tsym.name)) {
+                buf.append(t.tsym.name);
+            }
+        } else if (o instanceof TypeSymbol) {
+            TypeSymbol tsym = (TypeSymbol)o;
+            if ((tsym.flags() & LAMBDA) != 0 && !buf.contains(tsym.name)) {
+                buf.append(tsym.name);
+            }
+        } else if (o instanceof JCDiagnostic) {
+            searchUninferredTypes((JCDiagnostic)o, buf);
+        } else if (o instanceof Iterable<?>) {
+            for (Object o2 : (Iterable<?>)o) {
+                searchUninferredTypesInArg(o2, buf);
+            }
+        }
+    }
+
     /**
      * phase 2:
      * Attribute a lambda expression with expected SAM type; return type/thrown
--- a/src/share/classes/com/sun/tools/javac/comp/Flow.java	Thu Sep 02 18:30:13 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Flow.java	Mon Sep 06 15:01:38 2010 +0100
@@ -1330,8 +1330,9 @@
                 alive = false;
             }
 
-            if (tree.getBodyKind() == JCLambda.BodyKind.STATEMENT &&
-                    alive && tree.type.getReturnType().tag != VOID) {
+            if (tree.getBodyKind() == JCLambda.BodyKind.STATEMENT && alive &&
+                    tree.type.getReturnType().tag != VOID &&
+                    tree.type.getReturnType().tag != NONE) {
                 log.error(TreeInfo.diagEndPos(tree.body), "missing.ret.stmt");
             }
             if (tree.type.getThrownTypes() == null) {
--- a/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu Sep 02 18:30:13 2010 +0100
+++ b/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Sep 06 15:01:38 2010 +0100
@@ -95,6 +95,12 @@
     lambda expression cannot be applied to given types\n\
     required: {0}\n\
     found: {1}
+compiler.warn.cyclic.lambda.inference.throws=\
+    thrown types cannot be inferred from lambda body because of cyclic inference\n\
+    explicit type required for the following parameter: {0}
+compiler.warn.cyclic.lambda.inference.throws.1=\
+    thrown types cannot be inferred from lambda body because of cyclic inference\n\
+    explicit types required for the following parameters: {0}
 compiler.err.invalid.target.type.for.lambda.conv=\
     invalid target type {0} for lambda conversion
 compiler.err.invalid.target.type.for.lambda.conv.1=\
--- a/test/tools/javac/diags/examples.not-yet.txt	Thu Sep 02 18:30:13 2010 +0100
+++ b/test/tools/javac/diags/examples.not-yet.txt	Mon Sep 06 15:01:38 2010 +0100
@@ -149,3 +149,5 @@
 compiler.misc.where.disjunctive                                                  #LAMBDA
 compiler.misc.where.disjunctive.1                                                #LAMBDA
 compiler.note.potential.lambda.found                                             #LAMBDA
+compiler.warn.cyclic.lambda.inference.throws                                     #LAMBDA
+compiler.warn.cyclic.lambda.inference.throws.1                                   #LAMBDA
--- a/test/tools/javac/lambda/LambdaExprNotVoid.java	Thu Sep 02 18:30:13 2010 +0100
+++ b/test/tools/javac/lambda/LambdaExprNotVoid.java	Mon Sep 06 15:01:38 2010 +0100
@@ -25,7 +25,7 @@
  * @test
  * @summary check that lambda expression body (when not a block) cannot be void
  * @author  Maurizio Cimadamore
- * @compile/fail/ref=LambdaExprNotVoid.out -XDrawDiagnostics LambdaExprNotVoid.java
+ * @compile/fail/ref=LambdaExprNotVoid.out -XDlambdaInferenceDiags=false -XDrawDiagnostics LambdaExprNotVoid.java
  */
 
 class LambdaExpr05 {
--- a/test/tools/javac/lambda/TargetType01.java	Thu Sep 02 18:30:13 2010 +0100
+++ b/test/tools/javac/lambda/TargetType01.java	Mon Sep 06 15:01:38 2010 +0100
@@ -25,7 +25,7 @@
  * @test
  * @summary show that method resolution is not NP hard
  * @author  Maurizio Cimadamore
- * @compile/fail/ref=TargetType01.out -XDrawDiagnostics TargetType01.java
+ * @compile/fail/ref=TargetType01.out -XDlambdaInferenceDiags=false -XDrawDiagnostics TargetType01.java
  */
 
 class TargetType01 {
--- a/test/tools/javac/lambda/TargetType13.java	Thu Sep 02 18:30:13 2010 +0100
+++ b/test/tools/javac/lambda/TargetType13.java	Mon Sep 06 15:01:38 2010 +0100
@@ -25,7 +25,7 @@
  * @test
  * @summary failure to infer exception thrown types from lambda body causes checked exception to be skipped
  * @author  Maurizio Cimadamore
- * @compile/fail/ref=TargetType13.out -XDrawDiagnostics TargetType13.java
+ * @compile/fail/ref=TargetType13.out -XDlambdaInferenceDiags=false -XDrawDiagnostics TargetType13.java
  */
 
 class TargetType13 {