Add basic support for the length() method of arrays

Support expressions where the expression that .length() is called on
does not have side effects.

Tested with WebGL 2 test sdk/tests/deqp/data/gles3/shaders/arrays.html

TEST=WebGL 2 conformance tests
BUG=angleproject:972

Change-Id: Ib4f8377a51da61179b6e47fbcf6b4d915e351fbd
Reviewed-on: https://chromium-review.googlesource.com/265654
Reviewed-by: Olli Etuaho <oetuaho@nvidia.com>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 31323b6..019b05e 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -3138,14 +3138,48 @@
     return intermediate.addBranch(op, returnValue, loc);
 }
 
-TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunction *fnCall, TIntermNode *node,
-    const TSourceLoc &loc, bool *fatalError)
+TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunction *fnCall, TIntermNode *paramNode, TIntermNode *thisNode,
+                                                     const TSourceLoc &loc, bool *fatalError)
 {
     *fatalError = false;
     TOperator op = fnCall->getBuiltInOp();
     TIntermTyped *callNode = nullptr;
 
-    if (op != EOpNull)
+    if (thisNode != nullptr)
+    {
+        ConstantUnion *unionArray = new ConstantUnion[1];
+        unsigned int arraySize = 0;
+        TIntermTyped *typedThis = thisNode->getAsTyped();
+        if (fnCall->getName() != "length")
+        {
+            error(loc, "invalid method", fnCall->getName().c_str());
+            recover();
+        }
+        else if (paramNode != nullptr)
+        {
+            error(loc, "method takes no parameters", "length");
+            recover();
+        }
+        else if (typedThis == nullptr || !typedThis->isArray())
+        {
+            error(loc, "length can only be called on arrays", "length");
+            recover();
+        }
+        else
+        {
+            arraySize = static_cast<unsigned int>(typedThis->getArraySize());
+            if (typedThis->hasSideEffects())
+            {
+                // This code path can be hit with an expression like this:
+                // (a = b).length()
+                // where a and b are both arrays.
+                UNIMPLEMENTED();
+            }
+        }
+        unionArray->setUConst(arraySize);
+        callNode = intermediate.addConstantUnion(unionArray, TType(EbtUInt, EbpUndefined, EvqConst), loc);
+    }
+    else if (op != EOpNull)
     {
         //
         // Then this should be a constructor.
@@ -3153,12 +3187,12 @@
         // Their parameters will be verified algorithmically.
         //
         TType type(EbtVoid, EbpUndefined);  // use this to get the type back
-        if (!constructorErrorCheck(loc, node, *fnCall, op, &type))
+        if (!constructorErrorCheck(loc, paramNode, *fnCall, op, &type))
         {
             //
             // It's a constructor, of type 'type'.
             //
-            callNode = addConstructor(node, &type, op, fnCall, loc);
+            callNode = addConstructor(paramNode, &type, op, fnCall, loc);
         }
 
         if (callNode == nullptr)
@@ -3197,21 +3231,21 @@
                     //
                     // Treat it like a built-in unary operator.
                     //
-                    callNode = createUnaryMath(op, node->getAsTyped(), loc, &fnCandidate->getReturnType());
+                    callNode = createUnaryMath(op, paramNode->getAsTyped(), loc, &fnCandidate->getReturnType());
                     if (callNode == nullptr)
                     {
                         std::stringstream extraInfoStream;
                         extraInfoStream << "built in unary operator function.  Type: "
-                            << static_cast<TIntermTyped*>(node)->getCompleteString();
+                            << static_cast<TIntermTyped*>(paramNode)->getCompleteString();
                         std::string extraInfo = extraInfoStream.str();
-                        error(node->getLine(), " wrong operand type", "Internal Error", extraInfo.c_str());
+                        error(paramNode->getLine(), " wrong operand type", "Internal Error", extraInfo.c_str());
                         *fatalError = true;
                         return nullptr;
                     }
                 }
                 else
                 {
-                    TIntermAggregate *aggregate = intermediate.setAggregateOperator(node, op, loc);
+                    TIntermAggregate *aggregate = intermediate.setAggregateOperator(paramNode, op, loc);
                     aggregate->setType(fnCandidate->getReturnType());
                     aggregate->setPrecisionFromChildren();
                     callNode = aggregate;
@@ -3224,7 +3258,7 @@
             {
                 // This is a real function call
 
-                TIntermAggregate *aggregate = intermediate.setAggregateOperator(node, EOpFunctionCall, loc);
+                TIntermAggregate *aggregate = intermediate.setAggregateOperator(paramNode, EOpFunctionCall, loc);
                 aggregate->setType(fnCandidate->getReturnType());
 
                 // this is how we know whether the given function is a builtIn function or a user defined function