/*
 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.javac.comp;

import java.util.EnumSet;
import java.util.Set;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.tools.JavaFileObject;

import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;

import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.code.Type.*;
import com.sun.tools.javac.tree.JCTree.*;

import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import static com.sun.tools.javac.code.TypeTag.FORALL;
import static com.sun.tools.javac.code.TypeTag.TYPEVAR;
import static com.sun.tools.javac.code.TypeTag.VOID;
import com.sun.tools.javac.model.LazyTreeLoader;

/** Resolves field, method and constructor header, and constructs corresponding Symbols.
 *
 *  <p><b>This is NOT part of any supported API.
 *  If you write code that depends on this, you do so at your own risk.
 *  This code and its internal interfaces are subject to change or
 *  deletion without notice.</b>
 */
public class MemberEnter extends JCTree.Visitor {
    protected static final Context.Key<MemberEnter> memberEnterKey = new Context.Key<>();

    private final Names names;
    private final Enter enter;
    private final Log log;
    private final Check chk;
    private final Attr attr;
    private final Symtab syms;
    private final Annotate annotate;
    private final Types types;
    private final DeferredLintHandler deferredLintHandler;
    private final LazyTreeLoader treeLoader;

    public static MemberEnter instance(Context context) {
        MemberEnter instance = context.get(memberEnterKey);
        if (instance == null)
            instance = new MemberEnter(context);
        return instance;
    }

    protected MemberEnter(Context context) {
        context.put(memberEnterKey, this);
        names = Names.instance(context);
        enter = Enter.instance(context);
        log = Log.instance(context);
        chk = Check.instance(context);
        attr = Attr.instance(context);
        syms = Symtab.instance(context);
        annotate = Annotate.instance(context);
        types = Types.instance(context);
        deferredLintHandler = DeferredLintHandler.instance(context);
        treeLoader = LazyTreeLoader.instance(context);
    }

    /** Construct method type from method signature.
     *  @param typarams    The method's type parameters.
     *  @param params      The method's value parameters.
     *  @param res             The method's result type,
     *                 null if it is a constructor.
     *  @param recvparam       The method's receiver parameter,
     *                 null if none given; TODO: or already set here?
     *  @param thrown      The method's thrown exceptions.
     *  @param env             The method's (local) environment.
     */
    Type signature(MethodSymbol msym,
                   List<JCTypeParameter> typarams,
                   List<JCVariableDecl> params,
                   JCTree res,
                   JCVariableDecl recvparam,
                   List<JCExpression> thrown,
                   Env<AttrContext> env) {

        // Enter and attribute type parameters.
        List<Type> tvars = enter.classEnter(typarams, env);
        attr.attribTypeVariables(typarams, env);

        // Enter and attribute value parameters.
        ListBuffer<Type> argbuf = new ListBuffer<>();
        for (List<JCVariableDecl> l = params; l.nonEmpty(); l = l.tail) {
            memberEnter(l.head, env);
            argbuf.append(l.head.vartype.type);
        }

        // Attribute result type, if one is given.
        Type restype = res == null ? syms.voidType : attr.attribType(res, env);

        // Attribute receiver type, if one is given.
        Type recvtype;
        if (recvparam!=null) {
            memberEnter(recvparam, env);
            recvtype = recvparam.vartype.type;
        } else {
            recvtype = null;
        }

        // Attribute thrown exceptions.
        ListBuffer<Type> thrownbuf = new ListBuffer<>();
        for (List<JCExpression> l = thrown; l.nonEmpty(); l = l.tail) {
            Type exc = attr.attribType(l.head, env);
            if (!exc.hasTag(TYPEVAR)) {
                exc = chk.checkClassType(l.head.pos(), exc);
            } else if (exc.tsym.owner == msym) {
                //mark inference variables in 'throws' clause
                exc.tsym.flags_field |= THROWS;
            }
            thrownbuf.append(exc);
        }
        MethodType mtype = new MethodType(argbuf.toList(),
                                    restype,
                                    thrownbuf.toList(),
                                    syms.methodClass);
        mtype.recvtype = recvtype;

        return tvars.isEmpty() ? mtype : new ForAll(tvars, mtype);
    }

/* ********************************************************************
 * Visitor methods for member enter
 *********************************************************************/

    /** Visitor argument: the current environment
     */
    protected Env<AttrContext> env;

    /** Enter field and method definitions and process import
     *  clauses, catching any completion failure exceptions.
     */
    protected void memberEnter(JCTree tree, Env<AttrContext> env) {
        Env<AttrContext> prevEnv = this.env;
        try {
            this.env = env;
            tree.accept(this);
        }  catch (CompletionFailure ex) {
            chk.completionError(tree.pos(), ex);
        } finally {
            this.env = prevEnv;
        }
    }

    /** Enter members from a list of trees.
     */
    void memberEnter(List<? extends JCTree> trees, Env<AttrContext> env) {
        for (List<? extends JCTree> l = trees; l.nonEmpty(); l = l.tail)
            memberEnter(l.head, env);
    }

    boolean hasSameBounds(ForAll t, ForAll s) {
        List<Type> l1 = t.tvars;
        List<Type> l2 = s.tvars;
        while (l1.nonEmpty() && l2.nonEmpty() &&
               isSameAPType(l1.head.getUpperBound(),
                          types.subst(l2.head.getUpperBound(),
                                s.tvars,
                                t.tvars))) {
            l1 = l1.tail;
            l2 = l2.tail;
        }
        return l1.isEmpty() && l2.isEmpty();
    }
    
    private boolean isSameAPType(Type t, Type s) {
        return    t != null && !t.hasTag(TypeTag.UNKNOWN) && types.isSameType(t, s)
               || t.tsym != null && s.tsym != null && s.isErroneous() && t.tsym.name == s.tsym.name;
    }
    
    private boolean isSameMethod(Type ot, Type os) {
        if (ot.getTag() != os.getTag()) return false;
        
        switch (ot.getTag()) {
            case METHOD: {
                MethodType t = (MethodType) ot;
                MethodType s = (MethodType) os;

                if (t.getParameterTypes().size() != s.getParameterTypes().size()) return false;
                if (!isSameAPType(t.getReturnType(), s.getReturnType())) return false;

                List<Type> tp = t.getParameterTypes();
                List<Type> sp = s.getParameterTypes();

                while (!tp.isEmpty() && !sp.isEmpty()) {
                    if (!isSameAPType(tp.head, sp.head)) return false;
                    tp = tp.tail;
                    sp = sp.tail;
                }

                return tp.isEmpty() == sp.isEmpty();
            }
            case FORALL: {
                ForAll t = (ForAll) ot;
                ForAll s = (ForAll) os;

                return    hasSameBounds(t, s)
                       && isSameMethod(t.qtype, types.subst(s.qtype, s.tvars, t.tvars));
            }
            default:
                return false;
        }
    }
    
    public void visitMethodDef(JCMethodDecl tree) {
        WriteableScope enclScope = enter.enterScope(env);
        MethodSymbol m = new MethodSymbol(0, tree.name, null, enclScope.owner);
        m.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, m, tree);
        tree.sym = m;

        //if this is a default method, add the DEFAULT flag to the enclosing interface
        if ((tree.mods.flags & DEFAULT) != 0) {
            m.enclClass().flags_field |= DEFAULT;
        }

        Env<AttrContext> localEnv = methodEnv(tree, env);
        DiagnosticPosition prevLintPos = deferredLintHandler.setPos(tree.pos());
        try {
            // Compute the method type
            m.type = signature(m, tree.typarams, tree.params,
                               tree.restype, tree.recvparam,
                               tree.thrown,
                               localEnv);
        } finally {
            deferredLintHandler.setPos(prevLintPos);
        }

        if (types.isSignaturePolymorphic(m)) {
            m.flags_field |= SIGNATURE_POLYMORPHIC;
        }
        if ((enclScope.owner.flags_field & FROMCLASS) != 0) {
            for (Symbol sym : enclScope.getSymbolsByName(tree.name)) {
                if (sym.kind == MTH) {
                    boolean sameType = (enclScope.owner.flags_field & APT_CLEANED) != 0 ? isSameMethod(m.type, sym.type) : types.isSameType(m.type, sym.type);
                    if (sameType) {
                        if ((sym.flags_field & FROMCLASS) != 0) {
                            treeCleaner.scan(tree);
                            tree.sym = (MethodSymbol)sym;
                            localEnv = methodEnv(tree, env);
                            tree.sym.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, tree.sym, tree) | (tree.sym.flags_field & APT_CLEANED);
                            tree.sym.flags_field |= FROMCLASS;
                            if (tree.sym.type.hasTag(FORALL)) {
                                for(List<Type> tvars = ((ForAll)tree.sym.type).tvars; tvars.nonEmpty(); tvars = tvars.tail)
                                    localEnv.info.scope.enter(tvars.head.tsym);
                            }
                            List<VarSymbol> p = tree.sym.params().reverse();
                            if (p != null) {
                                List<JCVariableDecl> l = tree.params.reverse();
                                while(l.nonEmpty() && p.nonEmpty()) {
                                    p.head.setName(l.head.name);
                                    if (l.head.getModifiers() != null && l.head.getModifiers().getFlags().contains(Modifier.FINAL)) {
                                        //copy the final flag, as the symbol might have come from the classfile:
                                        p.head.flags_field |= FINAL;
                                    }
                                    localEnv.info.scope.enter(p.head);
                                    p.head.flags_field |= FROMCLASS;
                                    p = p.tail;
                                    l = l.tail;
                                }
                                while(p.nonEmpty()) {
                                    p.head.setName(p.head.name);
                                    localEnv.info.scope.enter(p.head);
                                    p = p.tail;
                                }
                            }
                            prevLintPos = deferredLintHandler.setPos(tree.pos());
                            try {
                                tree.sym.type = signature(tree.sym, tree.typarams, tree.params,
                                        tree.restype, tree.recvparam, tree.thrown,
                                        localEnv);
                            } finally {
                                deferredLintHandler.setPos(prevLintPos);
                            }
                            tree.sym.flags_field &= ~FROMCLASS;
                            localEnv.info.scope.leave();

                            // Set m.params
                            ListBuffer<VarSymbol> params = new ListBuffer<>();
                            JCVariableDecl lastParam = null;
                            for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
                                JCVariableDecl param = lastParam = l.head;
                                params.append(Assert.checkNonNull(param.sym));
                            }
                            tree.sym.params = params.toList();

                            // mark the method varargs, if necessary
                            if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0)
                                tree.sym.flags_field |= Flags.VARARGS;
                            tree.sym.flags_field &= ~APT_CLEANED;
                        }
                        break;
                    }
                }
            }
            if (tree.sym == m) {
                if ((enclScope.owner.flags_field & APT_CLEANED) == 0 && (m.name != names.init || !m.owner.owner.kind.matches(KindSelector.VAL_MTH))) {
                    ClassSymbol cs = enclScope.owner.outermostClass();
                    treeLoader.couplingError(cs, tree);
                } else {
                    localEnv.info.scope.leave();
                    // Set m.params
                    ListBuffer<VarSymbol> params = new ListBuffer<>();
                    JCVariableDecl lastParam = null;
                    for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
                        JCVariableDecl param = lastParam = l.head;
                        params.append(Assert.checkNonNull(param.sym));
                    }
                    m.params = params.toList();

                    // mark the method varargs, if necessary
                    if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0)
                        m.flags_field |= Flags.VARARGS;

                    if( chk.checkUnique(tree.pos(), m, enclScope))
                        enclScope.enter(m);
                }
            }
        } else {
            // Set m.params
            ListBuffer<VarSymbol> params = new ListBuffer<>();
            JCVariableDecl lastParam = null;
            for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
                JCVariableDecl param = lastParam = l.head;
                params.append(Assert.checkNonNull(param.sym));
            }
            m.params = params.toList();

            // mark the method varargs, if necessary
            if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0)
                m.flags_field |= Flags.VARARGS;

            localEnv.info.scope.leave();
            if (chk.checkUnique(tree.pos(), m, enclScope)) {
            enclScope.enter(m);
            }
        }
        annotate.annotateLater(tree.mods.annotations, localEnv, tree.sym, tree.pos());
        // Visit the signature of the method. Note that
        // TypeAnnotate doesn't descend into the body.
        annotate.queueScanTreeAndTypeAnnotate(tree, localEnv, tree.sym, tree.pos());

        if (tree.defaultValue != null) {
            tree.sym.defaultValue = annotate.unfinishedDefaultValue(); // set it to temporary sentinel for now
            annotate.annotateDefaultValueLater(tree.defaultValue, localEnv, tree.sym, tree.pos());
        }
    }

    /** Create a fresh environment for method bodies.
     *  @param tree     The method definition.
     *  @param env      The environment current outside of the method definition.
     */
    Env<AttrContext> methodEnv(JCMethodDecl tree, Env<AttrContext> env) {
        Env<AttrContext> localEnv =
            env.dup(tree, env.info.dup(env.info.scope.dupUnshared(tree.sym)));
        localEnv.enclMethod = tree;
        if (tree.sym.type != null) {
            //when this is called in the enter stage, there's no type to be set
            localEnv.info.returnResult = attr.new ResultInfo(KindSelector.VAL,
                                                             tree.sym.type.getReturnType());
        }
        if ((tree.mods.flags & STATIC) != 0) localEnv.info.staticLevel++;
        return localEnv;
    }

    public void visitVarDef(JCVariableDecl tree) {
        Env<AttrContext> localEnv = env;
        if ((tree.mods.flags & STATIC) != 0 ||
            (env.info.scope.owner.flags() & INTERFACE) != 0) {
            localEnv = env.dup(tree, env.info.dup());
            localEnv.info.staticLevel++;
        }
        DiagnosticPosition prevLintPos = deferredLintHandler.setPos(tree.pos());

        try {
            if (TreeInfo.isEnumInit(tree)) {
                attr.attribIdentAsEnumType(localEnv, (JCIdent)tree.vartype);
            } else {
                attr.attribType(tree.vartype, localEnv);
                if (TreeInfo.isReceiverParam(tree))
                    checkReceiver(tree, localEnv);
            }
        } finally {
            deferredLintHandler.setPos(prevLintPos);
        }

        if (tree.vartype.type != null && tree.vartype.type.hasTag(VOID)) {
            log.error(tree.vartype, "illegal.start.of.type");
            tree.vartype.type = syms.errType;
        }

        if ((tree.mods.flags & VARARGS) != 0) {
            //if we are entering a varargs parameter, we need to
            //replace its type (a plain array type) with the more
            //precise VarargsType --- we need to do it this way
            //because varargs is represented in the tree as a
            //modifier on the parameter declaration, and not as a
            //distinct type of array node.
            ArrayType atype = (ArrayType)tree.vartype.type;
            tree.vartype.type = atype.makeVarargs();
        }
        WriteableScope enclScope = enter.enterScope(env);
        VarSymbol v = null;
        boolean doEnterSymbol = true;
        if ((enclScope.owner.flags_field & FROMCLASS) != 0) {
            for (Symbol sym : enclScope.getSymbolsByName(tree.name)) {
                boolean sameType = (enclScope.owner.flags_field & APT_CLEANED) != 0 ? isSameAPType(tree.vartype.type, sym.type) : types.isSameType(tree.vartype.type, sym.type);
                if (sym.kind == VAR && sameType) {
                    if ((sym.flags_field & FROMCLASS) != 0) {
                        v = (VarSymbol)sym;
                        v.type = tree.vartype.type;
                        sym.flags_field &= ~FROMCLASS;
                    }
                    break;
                }
            }
            if (v != null) {
                doEnterSymbol = false;
            } else if ((enclScope.owner.flags_field & APT_CLEANED) == 0) {
                ClassSymbol cs = enclScope.owner.outermostClass();
                treeLoader.couplingError(cs, tree);
                doEnterSymbol = false;
            }
        }
        if (v == null) {
            v = new VarSymbol(0, tree.name, tree.vartype.type, enclScope.owner);
        }
        v.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, v, tree);
        tree.sym = v;
        if (tree.init != null) {
            v.flags_field |= HASINIT;
            if ((v.flags_field & FINAL) != 0 &&
                needsLazyConstValue(tree.init)) {
                Env<AttrContext> initEnv = getInitEnv(tree, env);
                initEnv.info.enclVar = v;
                v.setLazyConstValue(initEnv(tree, initEnv), attr, tree);
            }
        }
        if (doEnterSymbol) {
            if (chk.checkUnique(tree.pos(), v, enclScope)) {
                chk.checkTransparentVar(tree.pos(), v, enclScope);
                enclScope.enter(v);
            } else if (v.getKind() == ElementKind.LOCAL_VARIABLE) {
                enclScope.enter(v);
            }
        }

        annotate.annotateLater(tree.mods.annotations, localEnv, v, tree.pos());
        annotate.queueScanTreeAndTypeAnnotate(tree.vartype, localEnv, v, tree.pos());

        v.pos = tree.pos;
    }
    // where
    void checkType(JCTree tree, Type type, String diag) {
        if (!tree.type.isErroneous() && !types.isSameType(tree.type, type)) {
            log.error(tree, diag, type, tree.type);
        }
    }
    void checkReceiver(JCVariableDecl tree, Env<AttrContext> localEnv) {
        attr.attribExpr(tree.nameexpr, localEnv);
        MethodSymbol m = localEnv.enclMethod.sym;
        if (m.isConstructor()) {
            Type outertype = m.owner.owner.type;
            if (outertype.hasTag(TypeTag.METHOD)) {
                // we have a local inner class
                outertype = m.owner.owner.owner.type;
            }
            if (outertype.hasTag(TypeTag.CLASS)) {
                checkType(tree.vartype, outertype, "incorrect.constructor.receiver.type");
                checkType(tree.nameexpr, outertype, "incorrect.constructor.receiver.name");
            } else {
                log.error(tree, "receiver.parameter.not.applicable.constructor.toplevel.class");
            }
        } else {
            checkType(tree.vartype, m.owner.type, "incorrect.receiver.type");
            checkType(tree.nameexpr, m.owner.type, "incorrect.receiver.name");
        }
    }

    public boolean needsLazyConstValue(JCTree tree) {
        InitTreeVisitor initTreeVisitor = new InitTreeVisitor();
        tree.accept(initTreeVisitor);
        return initTreeVisitor.result;
    }

    /** Visitor class for expressions which might be constant expressions,
     *  as per JLS 15.28 (Constant Expressions).
     */
    static class InitTreeVisitor extends JCTree.Visitor {

        private static final Set<Tag> ALLOWED_OPERATORS =
                EnumSet.of(Tag.POS, Tag.NEG, Tag.NOT, Tag.COMPL, Tag.PLUS, Tag.MINUS,
                           Tag.MUL, Tag.DIV, Tag.MOD, Tag.SL, Tag.SR, Tag.USR,
                           Tag.LT, Tag.LE, Tag.GT, Tag.GE, Tag.EQ, Tag.NE,
                           Tag.BITAND, Tag.BITXOR, Tag.BITOR, Tag.AND, Tag.OR);

        private boolean result = true;

        @Override
        public void visitTree(JCTree tree) {
            result = false;
        }

        @Override
        public void visitLiteral(JCLiteral that) {}

        @Override
        public void visitTypeCast(JCTypeCast tree) {
            tree.expr.accept(this);
        }

        @Override
        public void visitUnary(JCUnary that) {
            if (!ALLOWED_OPERATORS.contains(that.getTag())) {
                result = false;
                return ;
            }
            that.arg.accept(this);
        }

        @Override
        public void visitBinary(JCBinary that) {
            if (!ALLOWED_OPERATORS.contains(that.getTag())) {
                result = false;
                return ;
            }
            that.lhs.accept(this);
            that.rhs.accept(this);
        }

        @Override
        public void visitConditional(JCConditional tree) {
            tree.cond.accept(this);
            tree.truepart.accept(this);
            tree.falsepart.accept(this);
        }

        @Override
        public void visitParens(JCParens tree) {
            tree.expr.accept(this);
        }

        @Override
        public void visitIdent(JCIdent that) {}

        @Override
        public void visitSelect(JCFieldAccess tree) {
            tree.selected.accept(this);
        }
    }

    /** Create a fresh environment for a variable's initializer.
     *  If the variable is a field, the owner of the environment's scope
     *  is be the variable itself, otherwise the owner is the method
     *  enclosing the variable definition.
     *
     *  @param tree     The variable definition.
     *  @param env      The environment current outside of the variable definition.
     */
    Env<AttrContext> initEnv(JCVariableDecl tree, Env<AttrContext> env) {
        Env<AttrContext> localEnv = env.dupto(new AttrContextEnv(tree, env.info.dup()));
        if (tree.sym.owner.kind == TYP || tree.sym.owner.kind == ERR) {
            localEnv.info.scope = env.info.scope.dupUnshared(tree.sym);
        }
        if ((tree.mods.flags & STATIC) != 0 ||
                ((env.enclClass.sym.flags() & INTERFACE) != 0 && env.enclMethod == null))
            localEnv.info.staticLevel++;
        return localEnv;
    }

    /** Default member enter visitor method: do nothing
     */
    public void visitTree(JCTree tree) {
    }

    public void visitErroneous(JCErroneous tree) {
        if (tree.errs != null)
            memberEnter(tree.errs, env);
    }

    public Env<AttrContext> getMethodEnv(JCMethodDecl tree, Env<AttrContext> env) {
        if (tree.sym == null)
            return null;
        Env<AttrContext> mEnv = methodEnv(tree, env);
        mEnv.info.lint = mEnv.info.lint.augment(tree.sym);
        for (List<JCTypeParameter> l = tree.typarams; l.nonEmpty(); l = l.tail)
            mEnv.info.scope.enterIfAbsent(l.head.type.tsym);
        for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail)
            mEnv.info.scope.enterIfAbsent(l.head.sym);
        return mEnv;
    }

    public Env<AttrContext> getInitEnv(JCVariableDecl tree, Env<AttrContext> env) {
        if (tree.sym == null)
            return null;
        Env<AttrContext> iEnv = initEnv(tree, env);
        return iEnv;
    }

    private static TreeScanner treeCleaner = new TreeScanner() {
        public void scan(JCTree node) {
            super.scan(node);
            if (node != null)
                node.type = null;
        }
        public void visitTopLevel(JCCompilationUnit node) {
            node.packge = null;
            super.visitTopLevel(node);
        }
        public void visitClassDef(JCClassDecl node) {
            node.sym = null;
            super.visitClassDef(node);
        }
        public void visitMethodDef(JCMethodDecl node) {
            node.sym = null;
            super.visitMethodDef(node);
        }
        public void visitVarDef(JCVariableDecl node) {
            node.sym = null;
            super.visitVarDef(node);
        }
        public void visitNewClass(JCNewClass node) {
            node.constructor = null;
            super.visitNewClass(node);
        }
        public void visitAssignop(JCAssignOp node) {
            node.operator = null;
            super.visitAssignop(node);
        }
        public void visitUnary(JCUnary node) {
            node.operator = null;
            super.visitUnary(node);
        }
        public void visitBinary(JCBinary node) {
            node.operator = null;
            super.visitBinary(node);
        }
        public void visitSelect(JCFieldAccess node) {
            node.sym = null;
            super.visitSelect(node);
        }
        public void visitIdent(JCIdent node) {
            node.sym = null;
            super.visitIdent(node);
        }
    };
}
