src/share/classes/com/sun/tools/javac/comp/Attr.java

changeset 1348
573ceb23beeb
parent 1347
1408af4cd8b0
child 1352
d4b3cb1ece84
     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 {

mercurial