JEXL-307: fixed lexical feature handling of variable scope in for/lambda units
Task #JEXL-307 - Variable redeclaration option
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
deleted file mode 100644
index 1ad61c0..0000000
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.jexl3.parser;
-
-import org.apache.commons.jexl3.internal.LexicalScope;
-
-/**
- * Declares a local variable.
- */
-public class ASTForeachStatement extends JexlNode implements JexlParser.LexicalUnit {
-    private LexicalScope locals = null;
-    
-    public ASTForeachStatement(int id) {
-        super(id);
-    }
-
-    public ASTForeachStatement(Parser p, int id) {
-        super(p, id);
-    }
-
-    @Override
-    public Object jjtAccept(ParserVisitor visitor, Object data) {
-        return visitor.visit(this, data);
-    }
-    
-    @Override
-    public boolean declareSymbol(int symbol) {
-        if (locals == null) {
-            locals  = new LexicalScope(null);
-        }
-        return locals.declareSymbol(symbol);
-    }
-    
-    @Override
-    public int getSymbolCount() {
-        return locals == null? 0 : locals.getSymbolCount();
-    }
-
-    @Override
-    public boolean hasSymbol(int symbol) {
-        return locals == null? false : locals.hasSymbol(symbol);
-    }    
-    
-    @Override
-    public void clearUnit() {
-        locals = null;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index 4aa2c05..d32feb5 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -61,7 +61,7 @@
     /**
      * When parsing inner functions/lambda, need to stack the scope (sic).
      */
-    protected Deque<Scope> frames = new ArrayDeque<Scope>();
+    protected final Deque<Scope> frames = new ArrayDeque<Scope>();
     /**
      * The list of pragma declarations.
      */
@@ -73,7 +73,7 @@
     /**
      * Stack of parsing loop counts.
      */
-    protected Deque<Integer> loopCounts = new ArrayDeque<Integer>();
+    protected final Deque<Integer> loopCounts = new ArrayDeque<Integer>();
     /**
      * Lexical unit merge, next block push is swallowed.
      */
@@ -85,7 +85,7 @@
     /**
      * Stack of lexical blocks.
      */
-    protected Deque<LexicalUnit> blocks = new ArrayDeque<LexicalUnit>();
+    protected final Deque<LexicalUnit> blocks = new ArrayDeque<LexicalUnit>();
 
     /**
      * A lexical unit is the container defining local symbols and their
@@ -242,19 +242,9 @@
 
     /**
      * Pushes a new lexical unit.
-     * <p>The merge flag allows the for(...) and lamba(...) constructs to
-     * merge in the next block since their loop-variable/parameter spill in the
-     * same lexical unit as their first block.
      * @param unit the new lexical unit
-     * @param merge whether the next unit merges in this one
      */
-    protected void pushUnit(LexicalUnit unit, boolean merge) {
-        if (merge) {
-            mergeBlock = true;
-        } else if (mergeBlock) {
-            mergeBlock = false;
-            return;
-        }
+    protected void pushUnit(LexicalUnit unit) {
         if (block != null) {
             blocks.push(block);
         }
@@ -262,14 +252,6 @@
     }
 
     /**
-     * Pushes a block as new lexical unit.
-     * @param unit the lexical unit
-     */
-    protected void pushUnit(LexicalUnit unit) {
-        pushUnit(unit, false);
-    }
-
-    /**
      * Restores the previous lexical unit.
      * @param unit restores the previous lexical scope
      */
@@ -295,8 +277,20 @@
             Integer symbol = frame.getSymbol(name);
             if (symbol != null) {
                 // can not reuse a local as a global
-                if (!block.hasSymbol(symbol) && getFeatures().isLexical()) {
-                    throw new JexlException(identifier,  name + ": variable is not defined");
+                if (getFeatures().isLexical()) {
+                    // one of the lexical blocks above must declare it
+                    if (!block.hasSymbol(symbol)) {
+                        boolean declared = false;
+                        for (LexicalUnit u : blocks) {
+                            if (u.hasSymbol(symbol)) {
+                                declared = true;
+                                break;
+                            }
+                        }
+                        if (!declared) {
+                            throw new JexlException(identifier, name + ": variable is not defined");
+                        }
+                    }
                 }
                 identifier.setSymbol(symbol, name);
             }
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index cdb19ca..a750261 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -296,7 +296,7 @@
 }
 {
    {
-        pushUnit(jjtThis, frame != null && frame.getArgCount() > 0);
+        pushUnit(jjtThis);
    }
         ( ( Statement() )*) <EOF>
    {
@@ -310,7 +310,7 @@
 }
 {   
    {
-        pushUnit(jjtThis, true);
+        pushUnit(jjtThis);
    }
    ( Expression() )? <EOF>
    {
@@ -399,7 +399,10 @@
 
 void ForeachStatement() : {}
 {
-    { pushUnit(jjtThis, true); } <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; popUnit(jjtThis); }
+    <FOR> <LPAREN> ForEachVar() <COLON>  Expression() <RPAREN>
+    { loopCount += 1; }
+        (LOOKAHEAD(1) Block() | Statement())
+    { loopCount -= 1; }
 }
 
 void ForEachVar() #Reference : {}
@@ -834,11 +837,11 @@
    pushFrame();
 }
 {
-  { pushUnit(jjtThis, true); } <FUNCTION> Parameters() Block() { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } <FUNCTION> Parameters() Block() { popUnit(jjtThis); }
   |
-  { pushUnit(jjtThis, true); } Parameters() <LAMBDA> Block() { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameters() <LAMBDA> Block() { popUnit(jjtThis); }
   |
-  { pushUnit(jjtThis, true); } Parameter() <LAMBDA> Block() { popUnit(jjtThis); }
+  { pushUnit(jjtThis); } Parameter() <LAMBDA> Block() { popUnit(jjtThis); }
 }
 
 
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index 8a6273f..64cfe6b 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -480,4 +480,18 @@
         Object result = s42.execute(jc);
         Assert.assertEquals(42, result);
     }
+        
+    @Test
+    public void testInnerAccess0() throws Exception {
+        JexlFeatures f = new JexlFeatures();
+        f.lexical(true);
+        JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+        JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { var c = 0; {return x; }} })();");
+    }
+    
+    @Test
+    public void testInnerAccess1() throws Exception {
+        JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
+        JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { var c = 0; {return x; }} })();");
+    }
 }