Browse Source

Add nodes to handle types (#49785)

This PR adds 3 nodes to handle types defined by a front-end creating a 
Painless AST. These types are decided with data immutability in mind - 
hence the reason for more than a single node.
Jack Conradson 5 years ago
parent
commit
f2d25f5737

+ 3 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

@@ -109,6 +109,7 @@ import org.elasticsearch.painless.lookup.PainlessLookup;
 import org.elasticsearch.painless.node.AExpression;
 import org.elasticsearch.painless.node.ANode;
 import org.elasticsearch.painless.node.AStatement;
+import org.elasticsearch.painless.node.DUnresolvedType;
 import org.elasticsearch.painless.node.EAssignment;
 import org.elasticsearch.painless.node.EBinary;
 import org.elasticsearch.painless.node.EBool;
@@ -478,8 +479,9 @@ public final class Walker extends PainlessParserBaseVisitor<ANode> {
         for (DeclvarContext declvar : ctx.declvar()) {
             String name = declvar.ID().getText();
             AExpression expression = declvar.expression() == null ? null : (AExpression)visit(declvar.expression());
+            DUnresolvedType unresolvedType = new DUnresolvedType(location(declvar), type);
 
-            declarations.add(new SDeclaration(location(declvar), type, name, expression));
+            declarations.add(new SDeclaration(location(declvar), unresolvedType, name, expression));
         }
 
         return new SDeclBlock(location(ctx), declarations);

+ 81 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/DResolvedType.java

@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.painless.node;
+
+import org.elasticsearch.painless.Location;
+import org.elasticsearch.painless.lookup.PainlessLookup;
+import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+
+import java.util.Objects;
+
+/**
+ * Represents a Painless type as a {@link Class}. This may still
+ * require resolution to ensure the type in the {@link PainlessLookup}.
+ */
+public class DResolvedType extends DType {
+
+    protected final Class<?> type;
+
+    /**
+     * If set to {@code true} ensures the type is in the {@link PainlessLookup}.
+     * If set to {@code false} assumes the type is valid.
+     */
+    protected final boolean checkInLookup;
+
+    public DResolvedType(Location location, Class<?> type) {
+        this(location, type, true);
+    }
+
+    public DResolvedType(Location location, Class<?> type, boolean checkInLookup) {
+        super(location);
+        this.type = Objects.requireNonNull(type);
+        this.checkInLookup = checkInLookup;
+    }
+
+    /**
+     * If {@link #checkInLookup} is {@code true} checks if the type is in the
+     * {@link PainlessLookup}, otherwise returns {@code this}.
+     * @throws IllegalArgumentException if both checking the type is in the {@link PainlessLookup}
+     * and the type cannot be resolved from the {@link PainlessLookup}
+     * @return a {@link DResolvedType} where the resolved Painless type is retrievable
+     */
+    @Override
+    public DResolvedType resolveType(PainlessLookup painlessLookup) {
+        if (checkInLookup == false) {
+            return this;
+        }
+
+        if (painlessLookup.getClasses().contains(type) == false) {
+            throw location.createError(new IllegalArgumentException(
+                    "cannot resolve type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "]"));
+        }
+
+        return new DResolvedType(location, type, false);
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return " (DResolvedType [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "])";
+    }
+}

+ 46 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/DType.java

@@ -0,0 +1,46 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.painless.node;
+
+import org.elasticsearch.painless.Location;
+import org.elasticsearch.painless.lookup.PainlessLookup;
+
+import java.util.Objects;
+
+/**
+ * Represents an abstract Painless type. {@link DType} nodes must be
+ * resolved using {@link #resolveType(PainlessLookup)} to retrieve the
+ * actual Painless type. {@link DType} exists as a base class so consumers
+ * may have either a {@link DUnresolvedType} representing a Painless
+ * canonical type name or a {@link DResolvedType} representing a Painless
+ * type as the Painless AST is constructed. This allows Painless types already
+ * resolved at the time of Painless AST construction to not be forced to
+ * convert back to a Painless canonical type name and then re-resolved.
+ */
+public abstract class DType {
+
+    protected final Location location;
+
+    public DType(Location location) {
+        this.location = Objects.requireNonNull(location);
+    }
+
+    public abstract DResolvedType resolveType(PainlessLookup painlessLookup);
+}

+ 61 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/DUnresolvedType.java

@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.painless.node;
+
+import org.elasticsearch.painless.Location;
+import org.elasticsearch.painless.lookup.PainlessLookup;
+
+import java.util.Objects;
+
+/**
+ * Represents a canonical Painless type name as a {@link String}
+ * that requires resolution.
+ */
+public class DUnresolvedType extends DType {
+
+    protected final String typeName;
+
+    public DUnresolvedType(Location location, String typeName) {
+        super(location);
+        this.typeName = Objects.requireNonNull(typeName);
+    }
+
+    /**
+     * Resolves the canonical Painless type name to a Painless type.
+     * @throws IllegalArgumentException if the type cannot be resolved from the {@link PainlessLookup}
+     * @return a {@link DResolvedType} where the resolved Painless type is retrievable
+     */
+    @Override
+    public DResolvedType resolveType(PainlessLookup painlessLookup) {
+        Class<?> type = painlessLookup.canonicalTypeNameToType(typeName);
+
+        if (type == null) {
+            throw location.createError(new IllegalArgumentException("cannot resolve type [" + typeName + "]"));
+        }
+
+        return new DResolvedType(location, type);
+    }
+
+    @Override
+    public String toString() {
+        return "(DUnresolvedType [" + typeName + "])";
+    }
+}
+ 

+ 5 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java

@@ -36,13 +36,13 @@ import java.util.Set;
  */
 public final class SDeclaration extends AStatement {
 
-    private final String type;
+    private final DType type;
     private final String name;
     private AExpression expression;
 
     private Variable variable = null;
 
-    public SDeclaration(Location location, String type, String name, AExpression expression) {
+    public SDeclaration(Location location, DType type, String name, AExpression expression) {
         super(location);
 
         this.type = Objects.requireNonNull(type);
@@ -61,19 +61,15 @@ public final class SDeclaration extends AStatement {
 
     @Override
     void analyze(ScriptRoot scriptRoot, Locals locals) {
-        Class<?> clazz = scriptRoot.getPainlessLookup().canonicalTypeNameToType(this.type);
-
-        if (clazz == null) {
-            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
-        }
+        DResolvedType resolvedType = type.resolveType(scriptRoot.getPainlessLookup());
 
         if (expression != null) {
-            expression.expected = clazz;
+            expression.expected = resolvedType.getType();
             expression.analyze(scriptRoot, locals);
             expression = expression.cast(scriptRoot, locals);
         }
 
-        variable = locals.addVariable(location, clazz, name, false);
+        variable = locals.addVariable(location, resolvedType.getType(), name, false);
     }
 
     @Override

+ 51 - 50
modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java

@@ -54,7 +54,7 @@ public class NodeToStringTests extends ESTestCase {
     public void testEAssignment() {
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SExpression (EAssignment (EVariable i) = (ENumeric 2)))\n"
               + "  (SReturn (EVariable i)))",
                 "def i;\n"
@@ -63,7 +63,7 @@ public class NodeToStringTests extends ESTestCase {
         for (String operator : new String[] {"+", "-", "*", "/", "%", "&", "^", "|", "<<", ">>", ">>>"}) {
             assertToString(
                     "(SClass\n"
-                  + "  (SDeclBlock (SDeclaration def i (ENumeric 1)))\n"
+                  + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i (ENumeric 1)))\n"
                   + "  (SExpression (EAssignment (EVariable i) " + operator + "= (ENumeric 2)))\n"
                   + "  (SReturn (EVariable i)))",
                     "def i = 1;\n"
@@ -73,31 +73,31 @@ public class NodeToStringTests extends ESTestCase {
         // Compound
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SReturn (EAssignment (EVariable i) = (ENumeric 2))))",
                 "def i;\n"
               + "return i = 2");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SReturn (EAssignment (EVariable i) ++ post)))",
                 "def i;\n"
               + "return i++");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SReturn (EAssignment (EVariable i) ++ pre)))",
                 "def i;\n"
               + "return ++i");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SReturn (EAssignment (EVariable i) -- post)))",
                 "def i;\n"
               + "return i--");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def i))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) i))\n"
               + "  (SReturn (EAssignment (EVariable i) -- pre)))",
                 "def i;\n"
               + "return --i");
@@ -153,7 +153,8 @@ public class NodeToStringTests extends ESTestCase {
     public void testECapturingFunctionRef() {
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration Integer x (PCallInvoke (EStatic Integer) valueOf (Args (ENumeric 5)))))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [Integer]) x "
+                + "(PCallInvoke (EStatic Integer) valueOf (Args (ENumeric 5)))))\n"
                 + "  (SReturn (PCallInvoke (PCallInvoke (EStatic Optional) empty) orElseGet (Args (ECapturingFunctionRef x toString)))))",
                   "Integer x = Integer.valueOf(5);\n"
                 + "return Optional.empty().orElseGet(x::toString)");
@@ -349,7 +350,7 @@ public class NodeToStringTests extends ESTestCase {
         assertToString("(SClass (SReturn (EVariable params)))", "return params");
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration def a (ENumeric 1)))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) a (ENumeric 1)))\n"
                 + "  (SReturn (EVariable a)))",
                   "def a = 1;\n"
                 + "return a");
@@ -373,13 +374,13 @@ public class NodeToStringTests extends ESTestCase {
         assertToString("(SClass (SReturn (PField nullSafe (EVariable params) a)))", "return params?.a");
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration int[] a (ENewArray int[] dims (Args (ENumeric 10)))))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [int[]]) a (ENewArray int[] dims (Args (ENumeric 10)))))\n"
                 + "  (SReturn (PField (EVariable a) length)))",
                   "int[] a = new int[10];\n"
                 + "return a.length");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration org.elasticsearch.painless.FeatureTestObject a"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [org.elasticsearch.painless.FeatureTestObject]) a"
               + " (ENewObj org.elasticsearch.painless.FeatureTestObject)))\n"
               + "  (SExpression (EAssignment (PField (EVariable a) x) = (ENumeric 10)))\n"
               + "  (SReturn (PField (EVariable a) x)))",
@@ -511,13 +512,13 @@ public class NodeToStringTests extends ESTestCase {
     public void testSBreak() {
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int itr (ENumeric 2)))\n"
-              + "  (SDeclBlock (SDeclaration int a (ENumeric 1)))\n"
-              + "  (SDeclBlock (SDeclaration int b (ENumeric 1)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) itr (ENumeric 2)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) a (ENumeric 1)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) b (ENumeric 1)))\n"
               + "  (SDo (EComp (EVariable b) < (ENumeric 1000)) (SBlock\n"
               + "    (SExpression (EAssignment (EVariable itr) ++ post))\n"
               + "    (SIf (EComp (EVariable itr) > (ENumeric 10000)) (SBlock (SBreak)))\n"
-              + "    (SDeclBlock (SDeclaration int tmp (EVariable a)))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) tmp (EVariable a)))\n"
               + "    (SExpression (EAssignment (EVariable a) = (EVariable b)))\n"
               + "    (SExpression (EAssignment (EVariable b) = (EBinary (EVariable tmp) + (EVariable b))))))\n"
               + "  (SReturn (EVariable b)))",
@@ -539,13 +540,13 @@ public class NodeToStringTests extends ESTestCase {
     public void testSContinue() {
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int itr (ENumeric 2)))\n"
-              + "  (SDeclBlock (SDeclaration int a (ENumeric 1)))\n"
-              + "  (SDeclBlock (SDeclaration int b (ENumeric 1)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) itr (ENumeric 2)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) a (ENumeric 1)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) b (ENumeric 1)))\n"
               + "  (SDo (EComp (EVariable b) < (ENumeric 1000)) (SBlock\n"
               + "    (SExpression (EAssignment (EVariable itr) ++ post))\n"
               + "    (SIf (EComp (EVariable itr) < (ENumeric 10000)) (SBlock (SContinue)))\n"
-              + "    (SDeclBlock (SDeclaration int tmp (EVariable a)))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) tmp (EVariable a)))\n"
               + "    (SExpression (EAssignment (EVariable a) = (EVariable b)))\n"
               + "    (SExpression (EAssignment (EVariable b) = (EBinary (EVariable tmp) + (EVariable b))))))\n"
               + "  (SReturn (EVariable b)))",
@@ -567,7 +568,7 @@ public class NodeToStringTests extends ESTestCase {
     public void testSDeclBlock() {
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration def a))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) a))\n"
                 + "  (SExpression (EAssignment (EVariable a) = (ENumeric 10)))\n"
                 + "  (SReturn (EVariable a)))",
                   "def a;\n"
@@ -575,34 +576,34 @@ public class NodeToStringTests extends ESTestCase {
                 + "return a");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration def a (ENumeric 10)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [def]) a (ENumeric 10)))\n"
               + "  (SReturn (EVariable a)))",
                 "def a = 10;\n"
               + "return a");
         assertToString(
                 "(SClass\n"
               + "  (SDeclBlock\n"
-              + "    (SDeclaration def a)\n"
-              + "    (SDeclaration def b)\n"
-              + "    (SDeclaration def c))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) a)\n"
+              + "    (SDeclaration (DUnresolvedType [def]) b)\n"
+              + "    (SDeclaration (DUnresolvedType [def]) c))\n"
               + "  (SReturn (EVariable a)))",
                 "def a, b, c;\n"
               + "return a");
         assertToString(
                 "(SClass\n"
               + "  (SDeclBlock\n"
-              + "    (SDeclaration def a (ENumeric 10))\n"
-              + "    (SDeclaration def b (ENumeric 20))\n"
-              + "    (SDeclaration def c (ENumeric 100)))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) a (ENumeric 10))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) b (ENumeric 20))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) c (ENumeric 100)))\n"
               + "  (SReturn (EVariable a)))",
                 "def a = 10, b = 20, c = 100;\n"
               + "return a");
         assertToString(
                 "(SClass\n"
               + "  (SDeclBlock\n"
-              + "    (SDeclaration def a (ENumeric 10))\n"
-              + "    (SDeclaration def b)\n"
-              + "    (SDeclaration def c (ENumeric 100)))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) a (ENumeric 10))\n"
+              + "    (SDeclaration (DUnresolvedType [def]) b)\n"
+              + "    (SDeclaration (DUnresolvedType [def]) c (ENumeric 100)))\n"
               + "  (SReturn (EVariable a)))",
                 "def a = 10, b, c = 100;\n"
               + "return a");
@@ -610,9 +611,9 @@ public class NodeToStringTests extends ESTestCase {
                 "(SClass\n"
               + "  (SIf (PField (EVariable params) a) (SBlock\n"
               + "    (SDeclBlock\n"
-              + "      (SDeclaration def a (ENumeric 10))\n"
-              + "      (SDeclaration def b)\n"
-              + "      (SDeclaration def c (ENumeric 100)))\n"
+              + "      (SDeclaration (DUnresolvedType [def]) a (ENumeric 10))\n"
+              + "      (SDeclaration (DUnresolvedType [def]) b)\n"
+              + "      (SDeclaration (DUnresolvedType [def]) c (ENumeric 100)))\n"
               + "    (SReturn (EVariable a))))\n"
               + "  (SReturn (EBoolean false)))",
                 "if (params.a) {"
@@ -625,12 +626,12 @@ public class NodeToStringTests extends ESTestCase {
     public void testSDo() {
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration int itr (ENumeric 2)))\n"
-                + "  (SDeclBlock (SDeclaration int a (ENumeric 1)))\n"
-                + "  (SDeclBlock (SDeclaration int b (ENumeric 1)))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) itr (ENumeric 2)))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) a (ENumeric 1)))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) b (ENumeric 1)))\n"
                 + "  (SDo (EComp (EVariable b) < (ENumeric 1000)) (SBlock\n"
                 + "    (SExpression (EAssignment (EVariable itr) ++ post))\n"
-                + "    (SDeclBlock (SDeclaration int tmp (EVariable a)))\n"
+                + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) tmp (EVariable a)))\n"
                 + "    (SExpression (EAssignment (EVariable a) = (EVariable b)))\n"
                 + "    (SExpression (EAssignment (EVariable b) = (EBinary (EVariable tmp) + (EVariable b))))))\n"
                 + "  (SReturn (EVariable b)))",
@@ -649,7 +650,7 @@ public class NodeToStringTests extends ESTestCase {
     public void testSEach() {
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int l (ENumeric 0)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) l (ENumeric 0)))\n"
               + "  (SEach String s (EListInit (EString 'cat') (EString 'dog') (EString 'chicken')) (SBlock "
                   + "(SExpression (EAssignment (EVariable l) += (PCallInvoke (EVariable s) length)))))\n"
               + "  (SReturn (EVariable l)))",
@@ -660,9 +661,9 @@ public class NodeToStringTests extends ESTestCase {
                 + "return l");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int l (ENumeric 0)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) l (ENumeric 0)))\n"
               + "  (SEach String s (EListInit (EString 'cat') (EString 'dog') (EString 'chicken')) (SBlock\n"
-              + "    (SDeclBlock (SDeclaration String s2 (EBinary (EString 'dire ') + (EVariable s))))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [String]) s2 (EBinary (EString 'dire ') + (EVariable s))))\n"
               + "    (SExpression (EAssignment (EVariable l) += (PCallInvoke (EVariable s2) length)))))\n"
               + "  (SReturn (EVariable l)))",
                 "int l = 0;\n"
@@ -676,9 +677,9 @@ public class NodeToStringTests extends ESTestCase {
     public void testSFor() {
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int sum (ENumeric 0)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) sum (ENumeric 0)))\n"
               + "  (SFor\n"
-              + "    (SDeclBlock (SDeclaration int i (ENumeric 0)))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 0)))\n"
               + "    (EComp (EVariable i) < (ENumeric 1000))\n"
               + "    (EAssignment (EVariable i) ++ post)\n"
               + "    (SBlock (SExpression (EAssignment (EVariable sum) += (EVariable i)))))\n"
@@ -690,13 +691,13 @@ public class NodeToStringTests extends ESTestCase {
                 + "return sum");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int sum (ENumeric 0)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) sum (ENumeric 0)))\n"
               + "  (SFor\n"
-              + "    (SDeclBlock (SDeclaration int i (ENumeric 0)))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 0)))\n"
               + "    (EComp (EVariable i) < (ENumeric 1000))\n"
               + "    (EAssignment (EVariable i) ++ post)\n"
               + "    (SBlock (SFor\n"
-              + "      (SDeclBlock (SDeclaration int j (ENumeric 0)))\n"
+              + "      (SDeclBlock (SDeclaration (DUnresolvedType [int]) j (ENumeric 0)))\n"
               + "      (EComp (EVariable j) < (ENumeric 1000))\n"
               + "      (EAssignment (EVariable j) ++ post)\n"
               + "      (SBlock (SExpression (EAssignment (EVariable sum) += (EBinary (EVariable i) * (EVariable j))))))))\n"
@@ -740,7 +741,7 @@ public class NodeToStringTests extends ESTestCase {
                 + "}");
         assertToString(
                 "(SClass\n"
-              + "  (SDeclBlock (SDeclaration int i (ENumeric 0)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 0)))\n"
               + "  (SIfElse (PField (EVariable param) a)\n"
               + "    (SBlock (SIfElse (PField (EVariable param) b)\n"
               + "      (SBlock (SReturn (EBoolean true)))\n"
@@ -789,7 +790,7 @@ public class NodeToStringTests extends ESTestCase {
     public void testSWhile() {
         assertToString(
                   "(SClass\n"
-                + "  (SDeclBlock (SDeclaration int i (ENumeric 0)))\n"
+                + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 0)))\n"
                 + "  (SWhile (EComp (EVariable i) < (ENumeric 10)) (SBlock (SExpression (EAssignment (EVariable i) ++ post))))\n"
                 + "  (SReturn (EVariable i)))",
                   "int i = 0;\n"
@@ -822,7 +823,7 @@ public class NodeToStringTests extends ESTestCase {
                 "(SClass\n"
               + "  (SFunction def a (Args (Pair int i) (Pair int j))\n"
               + "    (SIf (EComp (EVariable i) < (EVariable j)) (SBlock (SReturn (EBoolean true))))\n"
-              + "    (SDeclBlock (SDeclaration int k (EBinary (EVariable i) + (EVariable j))))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) k (EBinary (EVariable i) + (EVariable j))))\n"
               + "    (SReturn (EVariable k)))\n"
               + "  (SReturn (EBoolean true)))",
                 "def a(int i, int j) {\n"
@@ -860,7 +861,7 @@ public class NodeToStringTests extends ESTestCase {
                 + "}");
         assertToString(
                 "(SClass (STry (SBlock\n"
-              + "  (SDeclBlock (SDeclaration int i (ENumeric 1)))\n"
+              + "  (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 1)))\n"
               + "  (SReturn (ENumeric 1)))\n"
               + "  (SCatch Exception e (SBlock (SReturn (ENumeric 2))))))",
                 "try {\n"
@@ -872,7 +873,7 @@ public class NodeToStringTests extends ESTestCase {
         assertToString(
                 "(SClass (STry (SBlock (SReturn (ENumeric 1)))\n"
               + "  (SCatch Exception e (SBlock\n"
-              + "    (SDeclBlock (SDeclaration int i (ENumeric 1)))\n"
+              + "    (SDeclBlock (SDeclaration (DUnresolvedType [int]) i (ENumeric 1)))\n"
               + "    (SReturn (ENumeric 2))))))",
                 "try {\n"
               + "  return 1\n"