001    /*
002     * $Id: JSRVariableScopeCodeVisitor.java,v 1.24 2005/11/13 16:42:11 blackdrag Exp $
003     *
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     *
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     *
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *
033     */
034    
035    package org.codehaus.groovy.classgen;
036    
037    import java.lang.reflect.Modifier;
038    import java.lang.reflect.Field;
039    import java.lang.reflect.Method;
040    import java.util.HashMap;
041    import java.util.HashSet;
042    import java.util.Iterator;
043    import java.util.Set;
044    import java.util.List;
045    import org.codehaus.groovy.ast.ASTNode;
046    import org.codehaus.groovy.ast.ClassHelper;
047    import org.codehaus.groovy.ast.ClassNode;
048    import org.codehaus.groovy.ast.CodeVisitorSupport;
049    import org.codehaus.groovy.ast.CompileUnit;
050    import org.codehaus.groovy.ast.ConstructorNode;
051    import org.codehaus.groovy.ast.FieldNode;
052    import org.codehaus.groovy.ast.GroovyClassVisitor;
053    import org.codehaus.groovy.ast.MethodNode;
054    import org.codehaus.groovy.ast.Parameter;
055    import org.codehaus.groovy.ast.PropertyNode;
056    import org.codehaus.groovy.ast.expr.ClosureExpression;
057    import org.codehaus.groovy.ast.expr.DeclarationExpression;
058    import org.codehaus.groovy.ast.expr.Expression;
059    import org.codehaus.groovy.ast.expr.FieldExpression;
060    import org.codehaus.groovy.ast.expr.PropertyExpression;
061    import org.codehaus.groovy.ast.expr.VariableExpression;
062    import org.codehaus.groovy.ast.stmt.BlockStatement;
063    import org.codehaus.groovy.ast.stmt.CatchStatement;
064    import org.codehaus.groovy.ast.stmt.DoWhileStatement;
065    import org.codehaus.groovy.ast.stmt.ForStatement;
066    import org.codehaus.groovy.ast.stmt.Statement;
067    import org.codehaus.groovy.ast.stmt.WhileStatement;
068    import org.codehaus.groovy.control.SourceUnit;
069    import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
070    import org.codehaus.groovy.syntax.SyntaxException;
071    
072    import org.codehaus.groovy.ast.Variable;
073    
074    public class JSRVariableScopeCodeVisitor extends CodeVisitorSupport implements GroovyClassVisitor {
075    
076        private static class Var implements Variable{
077            //TODO: support final and native
078            String name;
079            ClassNode type=null;
080            boolean isInStaticContext=false;
081            boolean isDynamicTyped;
082            
083            public Var(String name,VarScope scope) {
084                // a Variable without type and other modifiers
085                // make it dynamic type, non final and non static
086                this.name=name;
087                setType(ClassHelper.DYNAMIC_TYPE);
088                isInStaticContext = scope.isInStaticContext;
089            }
090    
091            public Var(String pName, MethodNode f) {
092                name = pName;
093                setType(f.getReturnType());
094                isInStaticContext=f.isStatic();
095            }
096            
097            public Var(String pName, Method m) {
098                name = pName;
099                type = ClassHelper.make(m.getReturnType());
100                isInStaticContext=Modifier.isStatic(m.getModifiers());
101            }
102    
103            public Var(Field f) {
104                name = f.getName();
105                type = ClassHelper.make(f.getType());
106                isInStaticContext=Modifier.isStatic(f.getModifiers());
107            }
108    
109            public Var(Variable v) {
110                name=v.getName();
111                type=v.getType();
112                isInStaticContext=v.isInStaticContext();
113                isDynamicTyped=v.isDynamicTyped();
114            }
115    
116            public void setType(ClassNode cn) {
117                type = cn;
118                isDynamicTyped |= cn==ClassHelper.DYNAMIC_TYPE;
119            }
120            
121            public ClassNode getType() {
122                return type;
123            }
124    
125            public String getName() {
126                return name;
127            }
128    
129            public Expression getInitialExpression() {
130                return null;
131            }
132    
133            public boolean hasInitialExpression() {
134                return false;
135            }
136    
137            public boolean isInStaticContext() {
138                return isInStaticContext;
139            }
140    
141            public boolean isDynamicTyped() {
142                return isDynamicTyped;
143            }        
144        }
145        
146        private static class VarScope {
147            boolean isClass=true;
148            boolean isInStaticContext = false;
149            
150            VarScope parent;
151            HashMap declares = new HashMap();
152            HashMap visibles = new HashMap();
153            
154            public VarScope(boolean isClass, VarScope parent, boolean staticContext) {
155                this.isClass=isClass;
156                this.parent = parent;
157                isInStaticContext = staticContext;
158            }
159            
160            public VarScope(VarScope parent, boolean staticContext) {
161                this(false,parent,staticContext);
162            }
163            
164            public VarScope(VarScope parent) {
165                this(false,parent,parent!=null?parent.isInStaticContext:false);
166            }
167        }
168        
169        private static class JRoseCheck  extends CodeVisitorSupport{
170            boolean closureStarted=false;
171            boolean itUsed=false;
172            
173            public void visitClosureExpression(ClosureExpression expression) {
174                // don't visit subclosures if already in a closure
175                if (closureStarted) return;
176                closureStarted=true;
177                Parameter[] param = expression.getParameters();
178                for (int i=0; i<param.length; i++) {
179                    itUsed = (param[i].getName().equals("it")) && closureStarted || itUsed;
180                }
181                super.visitClosureExpression(expression);
182            }
183            
184            public void visitVariableExpression(VariableExpression expression) {
185                itUsed = (expression.getName().equals("it")) && closureStarted || itUsed;
186            }
187            
188        }
189        
190        private VarScope currentScope = null;
191        private CompileUnit unit;
192        private SourceUnit source; 
193        private boolean scriptMode=false;
194        private ClassNode currentClass=null;
195        
196        private boolean jroseRule=false;
197        
198        public JSRVariableScopeCodeVisitor(VarScope scope, SourceUnit source) {
199            //System.out.println("scope check enabled");
200            if ("true".equals(System.getProperty("groovy.jsr.check.rule.jrose"))) {
201                jroseRule=true;
202                //System.out.println("jrose check enabled");
203            }
204            currentScope = scope;
205            this.source = source;
206            if (source.getAST() == null) return;
207            this.unit = source.getAST().getUnit();
208        }
209    
210        public void visitBlockStatement(BlockStatement block) {
211            VarScope scope = currentScope;
212            currentScope = new VarScope(currentScope);
213            super.visitBlockStatement(block);
214            currentScope = scope;
215        }
216    
217        public void visitForLoop(ForStatement forLoop) {
218            VarScope scope = currentScope;
219            // TODO: always define a variable here? What about type?
220            currentScope = new VarScope(currentScope);
221            declare(new Var(forLoop.getVariable(),currentScope), forLoop);
222            super.visitForLoop(forLoop);
223            currentScope = scope;
224        }
225    
226        public void visitWhileLoop(WhileStatement loop) {
227            //TODO: check while loop variables
228            VarScope scope = currentScope;
229            currentScope = new VarScope(currentScope);
230            super.visitWhileLoop(loop);
231            currentScope = scope;
232        }
233    
234        public void visitDoWhileLoop(DoWhileStatement loop) {
235            //TODO: still existant?
236            VarScope scope = currentScope;
237            currentScope = new VarScope(currentScope);
238            super.visitDoWhileLoop(loop);
239            currentScope = scope;
240        }
241    
242        public void visitDeclarationExpression(DeclarationExpression expression) {
243            // visit right side first to avoid the usage of a 
244            // variable before its declaration
245            expression.getRightExpression().visit(this);
246            // no need to visit left side, just get the variable name
247            VariableExpression vex = expression.getVariableExpression();
248            vex.setInStaticContext(currentScope.isInStaticContext);
249            if (!jroseRule && "it".equals(vex.getName())) {
250                // we are not in jrose mode, so don't allow variables 
251                // of the name 'it'
252                addError("'it' is a keyword in this mode.",vex);
253            } else {
254                declare(vex);
255            }
256        }
257        
258        private void addError(String msg, ASTNode expr) {
259            int line = expr.getLineNumber();
260            int col = expr.getColumnNumber();
261            source.getErrorCollector().addErrorAndContinue(
262              new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
263            );
264        }
265    
266        private void declare(VariableExpression expr) {
267            declare(expr,expr);
268        }
269        
270        private void declare(Variable var, ASTNode expr) {
271            String scopeType = "scope";
272            String variableType = "variable";
273            
274            if (expr.getClass()==FieldNode.class){
275                scopeType = "class"; 
276                variableType = "field";
277            } else if (expr.getClass()==PropertyNode.class){
278                scopeType = "class"; 
279                variableType = "property";
280            }
281            
282            StringBuffer msg = new StringBuffer();
283            msg.append("The current ").append(scopeType);
284            msg.append(" does already contain a ").append(variableType);
285            msg.append(" of the name ").append(var.getName());
286            
287            if (currentScope.declares.get(var.getName())!=null) {
288                addError(msg.toString(),expr);
289                return;
290            }
291            
292            //TODO: this case is not visited I think
293            if (currentScope.isClass) {
294                currentScope.declares.put(var.getName(),var);
295            }
296            
297            for (VarScope scope = currentScope.parent; scope!=null; scope = scope.parent) {
298                HashMap declares = scope.declares;
299                if (scope.isClass) break;
300                if (declares.get(var.getName())!=null) {
301                    // variable already declared
302                    addError(msg.toString(), expr);
303                    break;
304                }
305            }
306            // declare the variable even if there was an error to allow more checks
307            currentScope.declares.put(var.getName(),var);
308        }
309        
310        public void visitVariableExpression(VariableExpression expression) {
311            String name = expression.getName();
312            Variable v = checkVariableNameForDeclaration(name,expression);
313            if (v==null) return;
314            checkVariableContextAccess(v,expression);
315        }
316        
317        public void visitFieldExpression(FieldExpression expression) {
318            String name = expression.getFieldName();
319            //TODO: change that to get the correct scope
320            Variable v = checkVariableNameForDeclaration(name,expression);
321            checkVariableContextAccess(v,expression);  
322        }
323        
324        private void checkAbstractDeclaration(MethodNode methodNode) {
325            if (!Modifier.isAbstract(methodNode.getModifiers())) return;
326            if (Modifier.isAbstract(currentClass.getModifiers())) return;
327            addError("Can't have an abstract method in a non abstract class." +
328                     " The class '" + currentClass.getName() +  "' must be declared abstract or the method '" +
329                     methodNode.getName() + "' must not be abstract.",methodNode);
330        }
331        
332        private boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) {
333            if (first.length!=second.length) return false;
334            for (int i=0; i<first.length; i++) {
335                String ft = first[i].getType().getName();
336                String st = second[i].getType().getName();
337                if (ft.equals(st)) continue;
338                return false;
339            }        
340            return true; 
341        }
342        
343        private void checkImplementsAndExtends(ClassNode node) {
344            ClassNode cn = node.getSuperClass();
345            if (cn.isInterface()) addError("you are not allowed to extend the Interface "+cn.getName()+", use implements instead", node);
346            ClassNode[] interfaces = node.getInterfaces();
347            for (int i = 0; i < interfaces.length; i++) {
348                cn = interfaces[i];
349                if (!cn.isInterface()) addError ("you are not allowed to implement the Class "+cn.getName()+", use extends instead", node); 
350            }
351        }
352        
353        private void checkClassForOverwritingFinal(ClassNode cn) {
354            ClassNode superCN = cn.getSuperClass();
355            if (superCN==null) return;
356            if (!Modifier.isFinal(superCN.getModifiers())) return;
357            StringBuffer msg = new StringBuffer();
358            msg.append("you are not allowed to overwrite the final class ");
359            msg.append(superCN.getName());
360            msg.append(".");
361            addError(msg.toString(),cn);
362            
363        }
364        
365        private void checkMethodsForOverwritingFinal(ClassNode cn) {
366            List l = cn.getMethods();     
367            for (Iterator cnIter = l.iterator(); cnIter.hasNext();) {
368                MethodNode method =(MethodNode) cnIter.next();
369                Parameter[] parameters = method.getParameters();
370                for (ClassNode superCN = cn.getSuperClass(); superCN!=null; superCN=superCN.getSuperClass()){
371                    List methods = superCN.getMethods(method.getName());
372                    for (Iterator iter = methods.iterator(); iter.hasNext();) {
373                        MethodNode m = (MethodNode) iter.next();
374                        Parameter[] np = m.getParameters();
375                        if (!hasEqualParameterTypes(parameters,np)) continue;
376                        if (!Modifier.isFinal(m.getModifiers())) return;
377                        
378                        StringBuffer msg = new StringBuffer();
379                        msg.append("you are not allowed to overwrite the final method ").append(method.getName());
380                        msg.append("(");
381                        boolean semi = false;
382                        for (int i=0; i<parameters.length;i++) {
383                            if (semi) {
384                                msg.append(",");
385                            } else {
386                                semi = true;
387                            }
388                            msg.append(parameters[i].getType());
389                        }
390                        msg.append(")");
391                        msg.append(" from class ").append(superCN.getName()); 
392                        msg.append(".");
393                        addError(msg.toString(),method);
394                        return;
395                    }
396                }
397            }        
398        }
399        
400        private void checkVariableContextAccess(Variable v, Expression expr) {
401            if (v.isInStaticContext() || !currentScope.isInStaticContext) return;        
402            
403            String msg =  v.getName()+
404                          " is declared in a dynamic context, but you tried to"+
405                          " access it from a static context.";
406            addError(msg,expr);
407            
408            // decalre a static variable to be able to continue the check
409            Var v2 = new Var(v);
410            v2.isInStaticContext = true;
411            currentScope.declares.put(v2.name,v2);
412        }
413        
414        private Variable checkVariableNameForDeclaration(VariableExpression expression) {
415            if (expression == VariableExpression.THIS_EXPRESSION) return null;
416            String name = expression.getName();
417            return checkVariableNameForDeclaration(name,expression);
418        }
419        
420        private Variable checkVariableNameForDeclaration(String name, Expression expression) {
421            Variable var = new Var(name,currentScope);
422            
423            // TODO: this line is not working
424            // if (expression==VariableExpression.SUPER_EXPRESSION) return;
425            if ("super".equals(var.getName()) || "this".equals(var.getName())) return null;
426            
427            VarScope scope = currentScope;
428            while (scope != null) {
429                if (scope.declares.get(var.getName())!=null) {
430                    var = (Variable) scope.declares.get(var.getName());
431                    break;
432                }
433                if (scope.visibles.get(var.getName())!=null) {
434                    var = (Variable) scope.visibles.get(var.getName());
435                    break;
436                }
437                // scope.getReferencedVariables().add(name);
438                scope = scope.parent;
439            }
440    
441            VarScope end = scope;
442    
443            if (scope == null) {
444                //TODO add a check to be on the lhs!
445                ClassNode vn = unit.getClass(var.getName());
446                // vn==null means there is no class of that name
447                // note: we need to do this check because it's possible in groovy to access
448                //       Classes without the .class known from Java. Example: def type = String;
449                if (vn==null) {
450                    declare(var,expression);
451                    // don't create an error when inside a script body 
452                    if (!scriptMode) addError("The variable " + var.getName() +
453                                              " is undefined in the current scope", expression);
454                }
455            } else {
456                scope = currentScope;
457                while (scope != end) {
458                    scope.visibles.put(var.getName(),var);
459                    scope = scope.parent;
460                }
461            }
462            
463            return var;
464        }
465    
466        public void visitClosureExpression(ClosureExpression expression) {
467            VarScope scope = currentScope;
468            currentScope = new VarScope(false,currentScope,scope.isInStaticContext);
469        
470            // TODO: set scope
471            // expression.setVarScope(currentScope);
472    
473            if (expression.isParameterSpecified()) {
474                Parameter[] parameters = expression.getParameters();
475                for (int i = 0; i < parameters.length; i++) {
476                    parameters[i].setInStaticContext(currentScope.isInStaticContext);
477                    declare(parameters[i],expression);
478                }
479            } else {
480                Var var = new Var("it",scope);
481                // TODO: when to add "it" and when not?
482                // John's rule is to add it only to the closures using 'it'
483                // and only to the closure itself, not to subclosures
484                if (jroseRule) {
485                    JRoseCheck check = new JRoseCheck();
486                    expression.visit(check);
487                    if (check.itUsed) declare(var,expression);
488                } else {                
489                    currentScope.declares.put("it",var);
490                }
491            }
492    
493            // currentScope = new VarScope(currentScope);
494            super.visitClosureExpression(expression);
495            currentScope = scope;
496        }
497    
498        public void visitClass(ClassNode node) {
499            checkImplementsAndExtends(node);
500            checkClassForOverwritingFinal(node);
501            checkMethodsForOverwritingFinal(node);
502            VarScope scope = currentScope;
503            currentScope = new VarScope(true,currentScope,false);
504            boolean scriptModeBackup = scriptMode;
505            scriptMode = node.isScript();
506            ClassNode classBackup = currentClass;
507            currentClass = node;
508            
509            HashMap declares = currentScope.declares;
510            // first pass, add all possible variable names (properies and fields)
511            // TODO: handle interfaces
512            // TODO: handle static imports
513            addVarNames(node);
514            addVarNames(node.getOuterClass(), currentScope.visibles, true);
515            addVarNames(node.getSuperClass(), currentScope.visibles, true);
516            // second pass, check contents
517            node.visitContents(this);
518            
519            currentClass = classBackup;
520            currentScope = scope;
521            scriptMode = scriptModeBackup;
522        }
523        
524        private void addVarNames(ClassNode cn) {
525            //TODO: change test for currentScope.declares
526            //TODO: handle indexed properties
527            if (cn == null) return;
528            List l = cn.getFields();
529            Set fields = new HashSet();        
530            for (Iterator iter = l.iterator(); iter.hasNext();) {
531                FieldNode f = (FieldNode) iter.next();
532                if (fields.contains(f)) {
533                    declare(f,f);
534                } else {
535                    fields.add(f);
536                    currentScope.declares.put(f.getName(),f);
537                }            
538            }
539    
540            //TODO: ignore double delcaration of methods for the moment
541            l = cn.getMethods();
542            Set setter = new HashSet();
543            Set getter = new HashSet();
544            for (Iterator iter = l.iterator(); iter.hasNext();) {
545                MethodNode f =(MethodNode) iter.next();
546                String methodName = f.getName();
547                String pName = getPropertyName(methodName);
548                if (pName == null) continue; 
549                Var var = new Var(pName,f);
550                currentScope.declares.put(var.name,var);
551            }
552    
553            l = cn.getProperties();
554            Set props = new HashSet();
555            for (Iterator iter = l.iterator(); iter.hasNext();) {
556                PropertyNode f = (PropertyNode) iter.next();
557                if (props.contains(f)) {
558                    declare(f,f);
559                } else {
560                    props.add(f);
561                    currentScope.declares.put(f.getName(),f);
562                } 
563            }
564        }
565    
566        private void addVarNames(ClassNode cn, HashMap refs, boolean visitParent){
567            // note this method is only called for parent classes
568            
569            if (cn == null) return;
570            List l = cn.getFields();
571            for (Iterator iter = l.iterator(); iter.hasNext();) {
572                FieldNode f = (FieldNode) iter.next();
573                if (visitParent && Modifier.isPrivate(f.getModifiers()))
574                    continue;
575                refs.put(f.getName(),f);
576            }
577            l = cn.getMethods();
578            for (Iterator iter = l.iterator(); iter.hasNext();) {
579                MethodNode f = (MethodNode) iter.next();
580                if (visitParent && Modifier.isPrivate(f.getModifiers()))
581                    continue;
582                String name = getPropertyName(f.getName());
583                if (name == null) continue;
584                refs.put(name, new Var(name,f));
585            }
586    
587            l = cn.getProperties();
588            for (Iterator iter = l.iterator(); iter.hasNext();) {
589                PropertyNode f = (PropertyNode) iter.next();
590                if (visitParent && Modifier.isPrivate(f.getModifiers()))
591                    continue;
592                refs.put(f.getName(),f);
593            }
594    
595            if (!visitParent) return;
596    
597            addVarNames(cn.getSuperClass(), refs, visitParent);
598            MethodNode enclosingMethod = cn.getEnclosingMethod();
599    
600            if (enclosingMethod == null) return;
601    
602            Parameter[] params = enclosingMethod.getParameters();
603            for (int i = 0; i < params.length; i++) {
604                refs.put(params[i].getName(),params[i]);
605            }
606    
607            if (visitParent)
608                addVarNames(enclosingMethod.getDeclaringClass(), refs, visitParent);
609    
610            addVarNames(cn.getOuterClass(), refs, visitParent);
611        }
612    
613        /*private void addVarNames(ClassNode superclassType, HashMap refs, boolean visitParent) 
614          throws ClassNotFoundException 
615        {
616    
617            if (superclassType == null) return;
618            String superclassName = superclassType.getName();
619    
620            ClassNode cn = unit.getClass(superclassName);
621            if (cn != null) {
622                addVarNames(cn, refs, visitParent);
623                return;
624            }
625    
626            Class c =  superclassType.getTypeClass();
627            if (c==null) c = unit.getClassLoader().loadClass(superclassName);
628            Field[] fields = c.getFields();
629            for (int i = 0; i < fields.length; i++) {
630                Field f = fields[i];
631                if (visitParent && Modifier.isPrivate(f.getModifiers()))
632                    continue;
633                refs.put(f.getName(),new Var(f));
634            }
635    
636            Method[] methods = c.getMethods();
637            for (int i = 0; i < methods.length; i++) {
638                Method m = methods[i];
639                if (visitParent && Modifier.isPrivate(m.getModifiers()))
640                    continue;
641                String name = getPropertyName(m.getName());
642                if (name == null) continue;
643                refs.put(name,new Var(name,m));
644            }
645    
646            if (!visitParent) return;
647    
648            addVarNames(c.getSuperclass(), refs, visitParent);
649    
650            // it's not possible to know the variable names used for an enclosing
651            // method
652    
653            // addVarNames(c.getEnclosingClass(),refs,visitParent);
654        }*/
655        
656        private String getPropertyName(String name) {
657            if (!(name.startsWith("set") || name.startsWith("get"))) return null;
658            String pname = name.substring(3);
659            if (pname.length() == 0) return null;
660            String s = pname.substring(0, 1).toLowerCase();
661            String rest = pname.substring(1);
662            return s + rest;
663        }    
664    
665        public void visitConstructor(ConstructorNode node) {
666            VarScope scope = currentScope;
667            currentScope = new VarScope(currentScope);
668            
669            // TODO: set scope
670            // node.setVarScope(currentScope);
671            
672            HashMap declares = currentScope.declares;
673            Parameter[] parameters = node.getParameters();
674            for (int i = 0; i < parameters.length; i++) {
675                // a constructor is never static
676                declare(parameters[i],node);
677            }
678            currentScope = new VarScope(currentScope);
679            Statement code = node.getCode();
680            if (code != null) code.visit(this);
681            currentScope = scope;
682        }
683        
684        public void visitMethod(MethodNode node) {
685            checkAbstractDeclaration(node);
686            
687            VarScope scope = currentScope;
688            currentScope = new VarScope(currentScope,node.isStatic());
689            
690            // TODO: set scope
691            // node.setVarScope(currentScope);
692            
693            HashMap declares = currentScope.declares;
694            Parameter[] parameters = node.getParameters();
695            for (int i = 0; i < parameters.length; i++) {
696                declares.put(parameters[i].getName(),parameters[i]);
697            }
698    
699            currentScope = new VarScope(currentScope);
700            Statement code = node.getCode();
701            if (code!=null) code.visit(this);
702            currentScope = scope;
703        }
704    
705        public void visitField(FieldNode node) {
706            Expression init = node.getInitialExpression();
707            if (init != null) init.visit(this);
708        }
709    
710        public void visitProperty(PropertyNode node) {
711            Statement statement = node.getGetterBlock();
712            if (statement != null) statement.visit(this);
713            
714            statement = node.getSetterBlock();
715            if (statement != null) statement.visit(this);
716            
717            Expression init = node.getInitialExpression();
718            if (init != null) init.visit(this);
719        }
720    
721        public void visitPropertyExpression(PropertyExpression expression) {}
722    
723        public void visitCatchStatement(CatchStatement statement) {
724            VarScope scope = currentScope;
725            currentScope = new VarScope(currentScope);
726            declare(new Var(statement.getVariable(),currentScope), statement);
727            super.visitCatchStatement(statement);
728            currentScope = scope;
729        }
730    
731    }