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

Thu, 25 Oct 2012 11:09:36 -0700

author
jjg
date
Thu, 25 Oct 2012 11:09:36 -0700
changeset 1374
c002fdee76fd
parent 1357
c75be5bc5283
child 1406
2901c7b5339e
permissions
-rw-r--r--

7200915: convert TypeTags from a series of small ints to an enum
Reviewed-by: jjg, mcimadamore
Contributed-by: vicente.romero@oracle.com

     1 /*
     2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package com.sun.tools.javac.comp;
    28 import com.sun.tools.javac.code.*;
    29 import com.sun.tools.javac.tree.*;
    30 import com.sun.tools.javac.util.*;
    31 import com.sun.tools.javac.code.Symbol.*;
    32 import com.sun.tools.javac.code.Type.*;
    33 import com.sun.tools.javac.comp.Attr.ResultInfo;
    34 import com.sun.tools.javac.comp.Infer.InferenceContext;
    35 import com.sun.tools.javac.comp.Resolve.MethodResolutionPhase;
    36 import com.sun.tools.javac.tree.JCTree.*;
    38 import javax.tools.JavaFileObject;
    40 import java.util.ArrayList;
    41 import java.util.HashSet;
    42 import java.util.Map;
    43 import java.util.Queue;
    44 import java.util.Set;
    45 import java.util.WeakHashMap;
    47 import static com.sun.tools.javac.code.TypeTag.DEFERRED;
    48 import static com.sun.tools.javac.code.TypeTag.NONE;
    49 import static com.sun.tools.javac.tree.JCTree.Tag.*;
    51 /**
    52  * This is an helper class that is used to perform deferred type-analysis.
    53  * Each time a poly expression occurs in argument position, javac attributes it
    54  * with a temporary 'deferred type' that is checked (possibly multiple times)
    55  * against an expected formal type.
    56  *
    57  *  <p><b>This is NOT part of any supported API.
    58  *  If you write code that depends on this, you do so at your own risk.
    59  *  This code and its internal interfaces are subject to change or
    60  *  deletion without notice.</b>
    61  */
    62 public class DeferredAttr extends JCTree.Visitor {
    63     protected static final Context.Key<DeferredAttr> deferredAttrKey =
    64         new Context.Key<DeferredAttr>();
    66     final Attr attr;
    67     final Check chk;
    68     final Enter enter;
    69     final Infer infer;
    70     final Log log;
    71     final Symtab syms;
    72     final TreeMaker make;
    73     final Types types;
    75     public static DeferredAttr instance(Context context) {
    76         DeferredAttr instance = context.get(deferredAttrKey);
    77         if (instance == null)
    78             instance = new DeferredAttr(context);
    79         return instance;
    80     }
    82     protected DeferredAttr(Context context) {
    83         context.put(deferredAttrKey, this);
    84         attr = Attr.instance(context);
    85         chk = Check.instance(context);
    86         enter = Enter.instance(context);
    87         infer = Infer.instance(context);
    88         log = Log.instance(context);
    89         syms = Symtab.instance(context);
    90         make = TreeMaker.instance(context);
    91         types = Types.instance(context);
    92     }
    94     /**
    95      * This type represents a deferred type. A deferred type starts off with
    96      * no information on the underlying expression type. Such info needs to be
    97      * discovered through type-checking the deferred type against a target-type.
    98      * Every deferred type keeps a pointer to the AST node from which it originated.
    99      */
   100     public class DeferredType extends Type {
   102         public JCExpression tree;
   103         Env<AttrContext> env;
   104         AttrMode mode;
   105         SpeculativeCache speculativeCache;
   107         DeferredType(JCExpression tree, Env<AttrContext> env) {
   108             super(DEFERRED, null);
   109             this.tree = tree;
   110             this.env = env.dup(tree, env.info.dup());
   111             this.speculativeCache = new SpeculativeCache();
   112         }
   114         /**
   115          * A speculative cache is used to keep track of all overload resolution rounds
   116          * that triggered speculative attribution on a given deferred type. Each entry
   117          * stores a pointer to the speculative tree and the resolution phase in which the entry
   118          * has been added.
   119          */
   120         class SpeculativeCache {
   122             private Map<Symbol, List<Entry>> cache =
   123                     new WeakHashMap<Symbol, List<Entry>>();
   125             class Entry {
   126                 JCTree speculativeTree;
   127                 Resolve.MethodResolutionPhase phase;
   129                 public Entry(JCTree speculativeTree, MethodResolutionPhase phase) {
   130                     this.speculativeTree = speculativeTree;
   131                     this.phase = phase;
   132                 }
   134                 boolean matches(Resolve.MethodResolutionPhase phase) {
   135                     return this.phase == phase;
   136                 }
   137             }
   139             /**
   140              * Clone a speculative cache entry as a fresh entry associated
   141              * with a new method (this maybe required to fixup speculative cache
   142              * misses after Resolve.access())
   143              */
   144             void dupAllTo(Symbol from, Symbol to) {
   145                 Assert.check(cache.get(to) == null);
   146                 List<Entry> entries = cache.get(from);
   147                 if (entries != null) {
   148                     cache.put(to, entries);
   149                 }
   150             }
   152             /**
   153              * Retrieve a speculative cache entry corresponding to given symbol
   154              * and resolution phase
   155              */
   156             Entry get(Symbol msym, MethodResolutionPhase phase) {
   157                 List<Entry> entries = cache.get(msym);
   158                 if (entries == null) return null;
   159                 for (Entry e : entries) {
   160                     if (e.matches(phase)) return e;
   161                 }
   162                 return null;
   163             }
   165             /**
   166              * Stores a speculative cache entry corresponding to given symbol
   167              * and resolution phase
   168              */
   169             void put(Symbol msym, JCTree speculativeTree, MethodResolutionPhase phase) {
   170                 List<Entry> entries = cache.get(msym);
   171                 if (entries == null) {
   172                     entries = List.nil();
   173                 }
   174                 cache.put(msym, entries.prepend(new Entry(speculativeTree, phase)));
   175             }
   176         }
   178         /**
   179          * Get the type that has been computed during a speculative attribution round
   180          */
   181         Type speculativeType(Symbol msym, MethodResolutionPhase phase) {
   182             SpeculativeCache.Entry e = speculativeCache.get(msym, phase);
   183             return e != null ? e.speculativeTree.type : Type.noType;
   184         }
   186         /**
   187          * Check a deferred type against a potential target-type. Depending on
   188          * the current attribution mode, a normal vs. speculative attribution
   189          * round is performed on the underlying AST node. There can be only one
   190          * speculative round for a given target method symbol; moreover, a normal
   191          * attribution round must follow one or more speculative rounds.
   192          */
   193         Type check(ResultInfo resultInfo) {
   194             DeferredAttrContext deferredAttrContext =
   195                     resultInfo.checkContext.deferredAttrContext();
   196             Assert.check(deferredAttrContext != emptyDeferredAttrContext);
   197             List<Type> stuckVars = stuckVars(tree, resultInfo);
   198             if (stuckVars.nonEmpty()) {
   199                 deferredAttrContext.addDeferredAttrNode(this, resultInfo, stuckVars);
   200                 return Type.noType;
   201             } else {
   202                 try {
   203                     switch (deferredAttrContext.mode) {
   204                         case SPECULATIVE:
   205                             Assert.check(mode == null ||
   206                                     (mode == AttrMode.SPECULATIVE &&
   207                                     speculativeType(deferredAttrContext.msym, deferredAttrContext.phase).hasTag(NONE)));
   208                             JCTree speculativeTree = attribSpeculative(tree, env, resultInfo);
   209                             speculativeCache.put(deferredAttrContext.msym, speculativeTree, deferredAttrContext.phase);
   210                             return speculativeTree.type;
   211                         case CHECK:
   212                             Assert.check(mode == AttrMode.SPECULATIVE);
   213                             return attr.attribTree(tree, env, resultInfo);
   214                     }
   215                     Assert.error();
   216                     return null;
   217                 } finally {
   218                     mode = deferredAttrContext.mode;
   219                 }
   220             }
   221         }
   222     }
   224     /**
   225      * The 'mode' in which the deferred type is to be type-checked
   226      */
   227     public enum AttrMode {
   228         /**
   229          * A speculative type-checking round is used during overload resolution
   230          * mainly to generate constraints on inference variables. Side-effects
   231          * arising from type-checking the expression associated with the deferred
   232          * type are reversed after the speculative round finishes. This means the
   233          * expression tree will be left in a blank state.
   234          */
   235         SPECULATIVE,
   236         /**
   237          * This is the plain type-checking mode. Produces side-effects on the underlying AST node
   238          */
   239         CHECK;
   240     }
   242     /**
   243      * Routine that performs speculative type-checking; the input AST node is
   244      * cloned (to avoid side-effects cause by Attr) and compiler state is
   245      * restored after type-checking. All diagnostics (but critical ones) are
   246      * disabled during speculative type-checking.
   247      */
   248     JCTree attribSpeculative(JCTree tree, Env<AttrContext> env, ResultInfo resultInfo) {
   249         JCTree newTree = new TreeCopier<Object>(make).copy(tree);
   250         Env<AttrContext> speculativeEnv = env.dup(newTree, env.info.dup(env.info.scope.dupUnshared()));
   251         speculativeEnv.info.scope.owner = env.info.scope.owner;
   252         Filter<JCDiagnostic> prevDeferDiagsFilter = log.deferredDiagFilter;
   253         Queue<JCDiagnostic> prevDeferredDiags = log.deferredDiagnostics;
   254         final JavaFileObject currentSource = log.currentSourceFile();
   255         try {
   256             log.deferredDiagnostics = new ListBuffer<JCDiagnostic>();
   257             log.deferredDiagFilter = new Filter<JCDiagnostic>() {
   258                 public boolean accepts(JCDiagnostic t) {
   259                     return t.getDiagnosticSource().getFile().equals(currentSource);
   260                 }
   261             };
   262             attr.attribTree(newTree, speculativeEnv, resultInfo);
   263             unenterScanner.scan(newTree);
   264             return newTree;
   265         } catch (Abort ex) {
   266             //if some very bad condition occurred during deferred attribution
   267             //we should dump all errors before killing javac
   268             log.reportDeferredDiagnostics();
   269             throw ex;
   270         } finally {
   271             unenterScanner.scan(newTree);
   272             log.deferredDiagFilter = prevDeferDiagsFilter;
   273             log.deferredDiagnostics = prevDeferredDiags;
   274         }
   275     }
   276     //where
   277         protected TreeScanner unenterScanner = new TreeScanner() {
   278             @Override
   279             public void visitClassDef(JCClassDecl tree) {
   280                 ClassSymbol csym = tree.sym;
   281                 enter.typeEnvs.remove(csym);
   282                 chk.compiled.remove(csym.flatname);
   283                 syms.classes.remove(csym.flatname);
   284                 super.visitClassDef(tree);
   285             }
   286         };
   288     /**
   289      * A deferred context is created on each method check. A deferred context is
   290      * used to keep track of information associated with the method check, such as
   291      * the symbol of the method being checked, the overload resolution phase,
   292      * the kind of attribution mode to be applied to deferred types and so forth.
   293      * As deferred types are processed (by the method check routine) stuck AST nodes
   294      * are added (as new deferred attribution nodes) to this context. The complete()
   295      * routine makes sure that all pending nodes are properly processed, by
   296      * progressively instantiating all inference variables on which one or more
   297      * deferred attribution node is stuck.
   298      */
   299     class DeferredAttrContext {
   301         /** attribution mode */
   302         final AttrMode mode;
   304         /** symbol of the method being checked */
   305         final Symbol msym;
   307         /** method resolution step */
   308         final Resolve.MethodResolutionPhase phase;
   310         /** inference context */
   311         final InferenceContext inferenceContext;
   313         /** list of deferred attribution nodes to be processed */
   314         ArrayList<DeferredAttrNode> deferredAttrNodes = new ArrayList<DeferredAttrNode>();
   316         DeferredAttrContext(AttrMode mode, Symbol msym, MethodResolutionPhase phase, InferenceContext inferenceContext) {
   317             this.mode = mode;
   318             this.msym = msym;
   319             this.phase = phase;
   320             this.inferenceContext = inferenceContext;
   321         }
   323         /**
   324          * Adds a node to the list of deferred attribution nodes - used by Resolve.rawCheckArgumentsApplicable
   325          * Nodes added this way act as 'roots' for the out-of-order method checking process.
   326          */
   327         void addDeferredAttrNode(final DeferredType dt, ResultInfo resultInfo, List<Type> stuckVars) {
   328             deferredAttrNodes.add(new DeferredAttrNode(dt, resultInfo, stuckVars));
   329         }
   331         /**
   332          * Incrementally process all nodes, by skipping 'stuck' nodes and attributing
   333          * 'unstuck' ones. If at any point no progress can be made (no 'unstuck' nodes)
   334          * some inference variable might get eagerly instantiated so that all nodes
   335          * can be type-checked.
   336          */
   337         void complete() {
   338             while (!deferredAttrNodes.isEmpty()) {
   339                 Set<Type> stuckVars = new HashSet<Type>();
   340                 boolean progress = false;
   341                 //scan a defensive copy of the node list - this is because a deferred
   342                 //attribution round can add new nodes to the list
   343                 for (DeferredAttrNode deferredAttrNode : List.from(deferredAttrNodes)) {
   344                     if (!deferredAttrNode.isStuck()) {
   345                         deferredAttrNode.process();
   346                         deferredAttrNodes.remove(deferredAttrNode);
   347                         progress = true;
   348                     } else {
   349                         stuckVars.addAll(deferredAttrNode.stuckVars);
   350                     }
   351                 }
   352                 if (!progress) {
   353                     //remove all variables that have already been instantiated
   354                     //from the list of stuck variables
   355                     inferenceContext.solveAny(inferenceContext.freeVarsIn(List.from(stuckVars)), types, infer);
   356                     inferenceContext.notifyChange(types);
   357                 }
   358             }
   359         }
   361         /**
   362          * Class representing a deferred attribution node. It keeps track of
   363          * a deferred type, along with the expected target type information.
   364          */
   365         class DeferredAttrNode implements Infer.InferenceContext.FreeTypeListener {
   367             /** underlying deferred type */
   368             DeferredType dt;
   370             /** underlying target type information */
   371             ResultInfo resultInfo;
   373             /** list of uninferred inference variables causing this node to be stuck */
   374             List<Type> stuckVars;
   376             DeferredAttrNode(DeferredType dt, ResultInfo resultInfo, List<Type> stuckVars) {
   377                 this.dt = dt;
   378                 this.resultInfo = resultInfo;
   379                 this.stuckVars = stuckVars;
   380                 if (!stuckVars.isEmpty()) {
   381                     resultInfo.checkContext.inferenceContext().addFreeTypeListener(stuckVars, this);
   382                 }
   383             }
   385             @Override
   386             public void typesInferred(InferenceContext inferenceContext) {
   387                 stuckVars = List.nil();
   388                 resultInfo = resultInfo.dup(inferenceContext.asInstType(resultInfo.pt, types));
   389             }
   391             /**
   392              * is this node stuck?
   393              */
   394             boolean isStuck() {
   395                 return stuckVars.nonEmpty();
   396             }
   398             /**
   399              * Process a deferred attribution node.
   400              * Invariant: a stuck node cannot be processed.
   401              */
   402             void process() {
   403                 if (isStuck()) {
   404                     throw new IllegalStateException("Cannot process a stuck deferred node");
   405                 }
   406                 dt.check(resultInfo);
   407             }
   408         }
   409     }
   411     /** an empty deferred attribution context - all methods throw exceptions */
   412     final DeferredAttrContext emptyDeferredAttrContext =
   413             new DeferredAttrContext(null, null, null, null) {
   414                 @Override
   415                 void addDeferredAttrNode(DeferredType dt, ResultInfo ri, List<Type> stuckVars) {
   416                     Assert.error("Empty deferred context!");
   417                 }
   418                 @Override
   419                 void complete() {
   420                     Assert.error("Empty deferred context!");
   421                 }
   422             };
   424     /**
   425      * Map a list of types possibly containing one or more deferred types
   426      * into a list of ordinary types. Each deferred type D is mapped into a type T,
   427      * where T is computed by retrieving the type that has already been
   428      * computed for D during a previous deferred attribution round of the given kind.
   429      */
   430     class DeferredTypeMap extends Type.Mapping {
   432         DeferredAttrContext deferredAttrContext;
   434         protected DeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
   435             super(String.format("deferredTypeMap[%s]", mode));
   436             this.deferredAttrContext = new DeferredAttrContext(mode, msym, phase, infer.emptyContext);
   437         }
   439         protected boolean validState(DeferredType dt) {
   440             return dt.mode != null &&
   441                     deferredAttrContext.mode.ordinal() <= dt.mode.ordinal();
   442         }
   444         @Override
   445         public Type apply(Type t) {
   446             if (!t.hasTag(DEFERRED)) {
   447                 return t.map(this);
   448             } else {
   449                 DeferredType dt = (DeferredType)t;
   450                 Assert.check(validState(dt));
   451                 return typeOf(dt);
   452             }
   453         }
   455         protected Type typeOf(DeferredType dt) {
   456             switch (deferredAttrContext.mode) {
   457                 case CHECK:
   458                     return dt.tree.type == null ? Type.noType : dt.tree.type;
   459                 case SPECULATIVE:
   460                     return dt.speculativeType(deferredAttrContext.msym, deferredAttrContext.phase);
   461             }
   462             Assert.error();
   463             return null;
   464         }
   465     }
   467     /**
   468      * Specialized recovery deferred mapping.
   469      * Each deferred type D is mapped into a type T, where T is computed either by
   470      * (i) retrieving the type that has already been computed for D during a previous
   471      * attribution round (as before), or (ii) by synthesizing a new type R for D
   472      * (the latter step is useful in a recovery scenario).
   473      */
   474     public class RecoveryDeferredTypeMap extends DeferredTypeMap {
   476         public RecoveryDeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
   477             super(mode, msym, phase);
   478         }
   480         @Override
   481         protected Type typeOf(DeferredType dt) {
   482             Type owntype = super.typeOf(dt);
   483             return owntype.hasTag(NONE) ?
   484                         recover(dt) : owntype;
   485         }
   487         @Override
   488         protected boolean validState(DeferredType dt) {
   489             return true;
   490         }
   492         /**
   493          * Synthesize a type for a deferred type that hasn't been previously
   494          * reduced to an ordinary type. Functional deferred types and conditionals
   495          * are mapped to themselves, in order to have a richer diagnostic
   496          * representation. Remaining deferred types are attributed using
   497          * a default expected type (j.l.Object).
   498          */
   499         private Type recover(DeferredType dt) {
   500             dt.check(attr.new RecoveryInfo(deferredAttrContext));
   501             switch (TreeInfo.skipParens(dt.tree).getTag()) {
   502                 case LAMBDA:
   503                 case REFERENCE:
   504                 case CONDEXPR:
   505                     //propagate those deferred types to the
   506                     //diagnostic formatter
   507                     return dt;
   508                 default:
   509                     return super.apply(dt);
   510             }
   511         }
   512     }
   514     /**
   515      * Retrieves the list of inference variables that need to be inferred before
   516      * an AST node can be type-checked
   517      */
   518     @SuppressWarnings("fallthrough")
   519     List<Type> stuckVars(JCTree tree, ResultInfo resultInfo) {
   520         if (resultInfo.pt.hasTag(NONE) || resultInfo.pt.isErroneous()) {
   521             return List.nil();
   522         } else {
   523             StuckChecker sc = new StuckChecker(resultInfo);
   524             sc.scan(tree);
   525             return List.from(sc.stuckVars);
   526         }
   527     }
   529     /**
   530      * This visitor is used to check that structural expressions conform
   531      * to their target - this step is required as inference could end up
   532      * inferring types that make some of the nested expressions incompatible
   533      * with their corresponding instantiated target
   534      */
   535     class StuckChecker extends TreeScanner {
   537         Type pt;
   538         Filter<JCTree> treeFilter;
   539         Infer.InferenceContext inferenceContext;
   540         Set<Type> stuckVars = new HashSet<Type>();
   542         final Filter<JCTree> argsFilter = new Filter<JCTree>() {
   543             public boolean accepts(JCTree t) {
   544                 switch (t.getTag()) {
   545                     case CONDEXPR:
   546                     case LAMBDA:
   547                     case PARENS:
   548                     case REFERENCE:
   549                         return true;
   550                     default:
   551                         return false;
   552                 }
   553             }
   554         };
   556         final Filter<JCTree> lambdaBodyFilter = new Filter<JCTree>() {
   557             public boolean accepts(JCTree t) {
   558                 switch (t.getTag()) {
   559                     case BLOCK: case CASE: case CATCH: case DOLOOP:
   560                     case FOREACHLOOP: case FORLOOP: case RETURN:
   561                     case SYNCHRONIZED: case SWITCH: case TRY: case WHILELOOP:
   562                         return true;
   563                     default:
   564                         return false;
   565                 }
   566             }
   567         };
   569         StuckChecker(ResultInfo resultInfo) {
   570             this.pt = resultInfo.pt;
   571             this.inferenceContext = resultInfo.checkContext.inferenceContext();
   572             this.treeFilter = argsFilter;
   573         }
   575         @Override
   576         public void scan(JCTree tree) {
   577             if (tree != null && treeFilter.accepts(tree)) {
   578                 super.scan(tree);
   579             }
   580         }
   582         @Override
   583         public void visitLambda(JCLambda tree) {
   584             Type prevPt = pt;
   585             Filter<JCTree> prevFilter = treeFilter;
   586             try {
   587                 if (inferenceContext.inferenceVars().contains(pt)) {
   588                     stuckVars.add(pt);
   589                 }
   590                 if (!types.isFunctionalInterface(pt.tsym)) {
   591                     return;
   592                 }
   593                 Type descType = types.findDescriptorType(pt);
   594                 List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
   595                 if (!TreeInfo.isExplicitLambda(tree) &&
   596                         freeArgVars.nonEmpty()) {
   597                     stuckVars.addAll(freeArgVars);
   598                 }
   599                 pt = descType.getReturnType();
   600                 if (tree.getBodyKind() == JCTree.JCLambda.BodyKind.EXPRESSION) {
   601                     scan(tree.getBody());
   602                 } else {
   603                     treeFilter = lambdaBodyFilter;
   604                     super.visitLambda(tree);
   605                 }
   606             } finally {
   607                 pt = prevPt;
   608                 treeFilter = prevFilter;
   609             }
   610         }
   612         @Override
   613         public void visitReference(JCMemberReference tree) {
   614             scan(tree.expr);
   615             if (inferenceContext.inferenceVars().contains(pt)) {
   616                 stuckVars.add(pt);
   617                 return;
   618             }
   619             if (!types.isFunctionalInterface(pt.tsym)) {
   620                 return;
   621             }
   622             Type descType = types.findDescriptorType(pt);
   623             List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
   624             stuckVars.addAll(freeArgVars);
   625         }
   627         @Override
   628         public void visitReturn(JCReturn tree) {
   629             Filter<JCTree> prevFilter = treeFilter;
   630             try {
   631                 treeFilter = argsFilter;
   632                 if (tree.expr != null) {
   633                     scan(tree.expr);
   634                 }
   635             } finally {
   636                 treeFilter = prevFilter;
   637             }
   638         }
   639     }
   640 }

mercurial