Edit

kc3-lang/angle/src/compiler/translator/ParseContext.cpp

Branch :

  • Show log

    Commit

  • Author : Brian Sheedy
    Date : 2025-09-17 15:57:09
    Hash : 44db20f2
    Message : Revert "Add a "noncoherent" layout qualifier to PLS" This reverts commit ef9c5ce43110f4f8ba56124bdee927944ce3ddd1. Reason for revert: Causes issues on Apple silicon crbug.com/445727125 Original change's description: > Add a "noncoherent" layout qualifier to PLS > > Raster ordering can be expensive. If an app knows the geometry in a draw > does not overlap, it can mark the PLS planes as noncoherent and opt for > using barriers instead. > > Bug: angleproject:40096838 > Change-Id: I2d686c2e64f51940face186637a9fcadcc251bef > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/6923300 > Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> > Reviewed-by: Kenneth Russell <kbr@chromium.org> > Commit-Queue: Chris Dalton <chris@rive.app> Bug: angleproject:40096838 No-Presubmit: true No-Tree-Checks: true No-Try: true Change-Id: Ibc24bed27cec406b6e4ffa9aaa8fe3a2110b58ba Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/6962955 Reviewed-by: Kenneth Russell <kbr@chromium.org> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Commit-Queue: Brian Sheedy <bsheedy@chromium.org>

  • src/compiler/translator/ParseContext.cpp
  • //
    // Copyright 2002 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    #ifdef UNSAFE_BUFFERS_BUILD
    #    pragma allow_unsafe_buffers
    #endif
    
    #include "compiler/translator/ParseContext.h"
    
    #include <stdarg.h>
    #include <stdio.h>
    
    #include "common/mathutil.h"
    #include "common/utilities.h"
    #include "compiler/preprocessor/SourceLocation.h"
    #include "compiler/translator/Declarator.h"
    #include "compiler/translator/StaticType.h"
    #include "compiler/translator/ValidateGlobalInitializer.h"
    #include "compiler/translator/ValidateSwitch.h"
    #include "compiler/translator/glslang.h"
    #include "compiler/translator/tree_util/IntermNode_util.h"
    #include "compiler/translator/util.h"
    
    namespace sh
    {
    
    ///////////////////////////////////////////////////////////////////////
    //
    // Sub- vector and matrix fields
    //
    ////////////////////////////////////////////////////////////////////////
    
    namespace
    {
    
    const int kWebGLMaxStructNesting = 4;
    
    struct IsSamplerFunc
    {
        bool operator()(TBasicType type) { return IsSampler(type); }
    };
    struct IsOpaqueFunc
    {
        bool operator()(TBasicType type) { return IsOpaqueType(type); }
    };
    
    template <typename OpaqueFunc>
    bool ContainsOpaque(const TStructure *structType);
    
    template <typename OpaqueFunc>
    bool ContainsOpaque(const TType &type)
    {
        if (OpaqueFunc{}(type.getBasicType()))
        {
            return true;
        }
        if (type.getBasicType() == EbtStruct)
        {
            return ContainsOpaque<OpaqueFunc>(type.getStruct());
        }
    
        return false;
    }
    
    template <typename OpaqueFunc>
    bool ContainsOpaque(const TStructure *structType)
    {
        for (const auto &field : structType->fields())
        {
            if (ContainsOpaque<OpaqueFunc>(*field->type()))
                return true;
        }
        return false;
    }
    
    // Get a token from an image argument to use as an error message token.
    const char *GetImageArgumentToken(TIntermTyped *imageNode)
    {
        ASSERT(IsImage(imageNode->getBasicType()));
        while (imageNode->getAsBinaryNode() &&
               (imageNode->getAsBinaryNode()->getOp() == EOpIndexIndirect ||
                imageNode->getAsBinaryNode()->getOp() == EOpIndexDirect))
        {
            imageNode = imageNode->getAsBinaryNode()->getLeft();
        }
        TIntermSymbol *imageSymbol = imageNode->getAsSymbolNode();
        if (imageSymbol)
        {
            return imageSymbol->getName().data();
        }
        return "image";
    }
    
    bool CanSetDefaultPrecisionOnType(const TPublicType &type)
    {
        if (!SupportsPrecision(type.getBasicType()))
        {
            return false;
        }
        if (type.getBasicType() == EbtUInt)
        {
            // ESSL 3.00.4 section 4.5.4
            return false;
        }
        if (type.isAggregate())
        {
            // Not allowed to set for aggregate types
            return false;
        }
        return true;
    }
    
    // Map input primitive types to input array sizes in a geometry shader.
    GLuint GetGeometryShaderInputArraySize(TLayoutPrimitiveType primitiveType)
    {
        switch (primitiveType)
        {
            case EptPoints:
                return 1u;
            case EptLines:
                return 2u;
            case EptTriangles:
                return 3u;
            case EptLinesAdjacency:
                return 4u;
            case EptTrianglesAdjacency:
                return 6u;
            default:
                UNREACHABLE();
                return 0u;
        }
    }
    
    bool IsBufferOrSharedVariable(TIntermTyped *var)
    {
        if (var->isInterfaceBlock() || var->getQualifier() == EvqBuffer ||
            var->getQualifier() == EvqShared)
        {
            return true;
        }
        return false;
    }
    
    TIntermTyped *FindLValueBase(TIntermTyped *node)
    {
        do
        {
            const TIntermBinary *binary = node->getAsBinaryNode();
            if (binary == nullptr)
            {
                return node;
            }
    
            TOperator op = binary->getOp();
            if (op != EOpIndexDirect && op != EOpIndexIndirect)
            {
                return static_cast<TIntermTyped *>(nullptr);
            }
    
            node = binary->getLeft();
        } while (true);
    }
    
    void AddAdvancedBlendEquation(gl::BlendEquationType eq, TLayoutQualifier *qualifier)
    {
        qualifier->advancedBlendEquations.set(static_cast<uint32_t>(eq));
    }
    
    constexpr bool IsValidWithPixelLocalStorage(TLayoutImageInternalFormat internalFormat)
    {
        switch (internalFormat)
        {
            case EiifRGBA8:
            case EiifRGBA8I:
            case EiifRGBA8UI:
            case EiifR32F:
            case EiifR32UI:
                return true;
            default:
                return false;
        }
    }
    
    constexpr ShPixelLocalStorageFormat ImageFormatToPLSFormat(TLayoutImageInternalFormat format)
    {
        switch (format)
        {
            default:
                return ShPixelLocalStorageFormat::NotPLS;
            case EiifRGBA8:
                return ShPixelLocalStorageFormat::RGBA8;
            case EiifRGBA8I:
                return ShPixelLocalStorageFormat::RGBA8I;
            case EiifRGBA8UI:
                return ShPixelLocalStorageFormat::RGBA8UI;
            case EiifR32F:
                return ShPixelLocalStorageFormat::R32F;
            case EiifR32UI:
                return ShPixelLocalStorageFormat::R32UI;
        }
    }
    
    bool UsesDerivatives(TIntermAggregate *functionCall)
    {
        const TOperator op = functionCall->getOp();
        if (BuiltInGroup::IsDerivativesFS(op))
        {
            return true;
        }
        switch (op)
        {
            // TextureFirstVersions with implicit LOD
            case EOpTexture2D:
            case EOpTexture2DProj:
            case EOpTextureCube:
            case EOpTexture3D:
            case EOpTexture3DProj:
            case EOpShadow2DEXT:
            case EOpShadow2DProjEXT:
            // TextureFirstVersionsBias
            case EOpTexture2DBias:
            case EOpTexture2DProjBias:
            case EOpTextureCubeBias:
            case EOpTexture3DBias:
            case EOpTexture3DProjBias:
            // TextureNoBias
            case EOpTexture:
            case EOpTextureProj:
            // TextureBias
            case EOpTextureBias:
            case EOpTextureProjBias:
            // TextureOffsetNoBias
            case EOpTextureOffset:
            case EOpTextureProjOffset:
            // TextureOffsetBias
            case EOpTextureOffsetBias:
            case EOpTextureProjOffsetBias:
            // TextureQueryLod
            case EOpTextureQueryLOD:
                return true;
            default:
                return false;
        }
    }
    
    bool IsSamplerOrStructWithOnlySamplers(const TType *type)
    {
        return IsSampler(type->getBasicType()) || type->isStructureContainingOnlySamplers();
    }
    }  // namespace
    
    // This tracks each binding point's current default offset for inheritance of subsequent
    // variables using the same binding, and keeps offsets unique and non overlapping.
    // See GLSL ES 3.1, section 4.4.6.
    class TParseContext::AtomicCounterBindingState
    {
      public:
        AtomicCounterBindingState() : mDefaultOffset(0) {}
        // Inserts a new span and returns -1 if overlapping, else returns the starting offset of
        // newly inserted span.
        int insertSpan(int start, size_t length)
        {
            gl::RangeI newSpan(start, start + static_cast<int>(length));
            for (const auto &span : mSpans)
            {
                if (newSpan.intersects(span))
                {
                    return -1;
                }
            }
            mSpans.push_back(newSpan);
            mDefaultOffset = newSpan.high();
            return start;
        }
        // Inserts a new span starting from the default offset.
        int appendSpan(size_t length) { return insertSpan(mDefaultOffset, length); }
        void setDefaultOffset(int offset) { mDefaultOffset = offset; }
    
      private:
        int mDefaultOffset;
        std::vector<gl::RangeI> mSpans;
    };
    
    TParseContext::TParseContext(TSymbolTable &symt,
                                 TExtensionBehavior &ext,
                                 sh::GLenum type,
                                 ShShaderSpec spec,
                                 const ShCompileOptions &options,
                                 TDiagnostics *diagnostics,
                                 const ShBuiltInResources &resources,
                                 ShShaderOutput outputType)
        : symbolTable(symt),
          mDeferredNonEmptyDeclarationErrorCheck(false),
          mShaderType(type),
          mShaderSpec(spec),
          mCompileOptions(options),
          mShaderVersion(100),
          mTreeRoot(nullptr),
          mLoopNestingLevel(0),
          mStructNestingLevel(0),
          mSwitchNestingLevel(0),
          mCurrentFunctionType(nullptr),
          mFunctionReturnsValue(false),
          mFragmentPrecisionHighOnESSL1(false),
          mEarlyFragmentTestsSpecified(false),
          mHasDiscard(false),
          mSampleQualifierSpecified(false),
          mPositionRedeclaredForSeparateShaderObject(false),
          mPointSizeRedeclaredForSeparateShaderObject(false),
          mPositionOrPointSizeUsedForSeparateShaderObject(false),
          mUsesDerivatives(false),
          mDefaultUniformMatrixPacking(EmpColumnMajor),
          mDefaultUniformBlockStorage(sh::IsWebGLBasedSpec(spec) ? EbsStd140 : EbsShared),
          mDefaultBufferMatrixPacking(EmpColumnMajor),
          mDefaultBufferBlockStorage(sh::IsWebGLBasedSpec(spec) ? EbsStd140 : EbsShared),
          mDiagnostics(diagnostics),
          mDirectiveHandler(ext, *mDiagnostics, mShaderVersion, mShaderType),
          mPreprocessor(mDiagnostics, &mDirectiveHandler, angle::pp::PreprocessorSettings(spec)),
          mScanner(nullptr),
          mMaxExpressionComplexity(static_cast<size_t>(options.limitExpressionComplexity
                                                           ? resources.MaxExpressionComplexity
                                                           : std::numeric_limits<size_t>::max())),
          mMaxStatementDepth(static_cast<size_t>(resources.MaxStatementDepth)),
          mMinProgramTexelOffset(resources.MinProgramTexelOffset),
          mMaxProgramTexelOffset(resources.MaxProgramTexelOffset),
          mMinProgramTextureGatherOffset(resources.MinProgramTextureGatherOffset),
          mMaxProgramTextureGatherOffset(resources.MaxProgramTextureGatherOffset),
          mComputeShaderLocalSizeDeclared(false),
          mComputeShaderLocalSize(-1),
          mNumViews(-1),
          mMaxNumViews(resources.MaxViewsOVR),
          mMaxImageUnits(resources.MaxImageUnits),
          mMaxCombinedTextureImageUnits(resources.MaxCombinedTextureImageUnits),
          mMaxUniformLocations(resources.MaxUniformLocations),
          mMaxUniformBufferBindings(resources.MaxUniformBufferBindings),
          mMaxVertexAttribs(resources.MaxVertexAttribs),
          mMaxAtomicCounterBindings(resources.MaxAtomicCounterBindings),
          mMaxAtomicCounterBufferSize(resources.MaxAtomicCounterBufferSize),
          mMaxShaderStorageBufferBindings(resources.MaxShaderStorageBufferBindings),
          mMaxPixelLocalStoragePlanes(resources.MaxPixelLocalStoragePlanes),
          mDeclaringFunction(false),
          mGeometryShaderInputPrimitiveType(EptUndefined),
          mGeometryShaderOutputPrimitiveType(EptUndefined),
          mGeometryShaderInvocations(0),
          mGeometryShaderMaxVertices(-1),
          mMaxGeometryShaderInvocations(resources.MaxGeometryShaderInvocations),
          mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices),
          mGeometryInputArraySize(0),
          mMaxPatchVertices(resources.MaxPatchVertices),
          mTessControlShaderOutputVertices(0),
          mTessEvaluationShaderInputPrimitiveType(EtetUndefined),
          mTessEvaluationShaderInputVertexSpacingType(EtetUndefined),
          mTessEvaluationShaderInputOrderingType(EtetUndefined),
          mTessEvaluationShaderInputPointType(EtetUndefined),
          mHasAnyPreciseType(false),
          mAdvancedBlendEquations(0),
          mFunctionBodyNewScope(false),
          mOutputType(outputType)
    {}
    
    TParseContext::~TParseContext() {}
    
    bool TParseContext::anyMultiviewExtensionAvailable()
    {
        return isExtensionEnabled(TExtension::OVR_multiview) ||
               isExtensionEnabled(TExtension::OVR_multiview2);
    }
    
    bool TParseContext::parseVectorFields(const TSourceLoc &line,
                                          const ImmutableString &compString,
                                          uint32_t vecSize,
                                          TVector<uint32_t> *fieldOffsets)
    {
        ASSERT(fieldOffsets);
        size_t fieldCount = compString.length();
        if (fieldCount > 4u)
        {
            error(line, "illegal vector field selection", compString);
            return false;
        }
        fieldOffsets->resize(fieldCount);
    
        enum
        {
            exyzw,
            ergba,
            estpq
        } fieldSet[4];
    
        for (unsigned int i = 0u; i < fieldOffsets->size(); ++i)
        {
            switch (compString[i])
            {
                case 'x':
                    (*fieldOffsets)[i] = 0;
                    fieldSet[i]        = exyzw;
                    break;
                case 'r':
                    (*fieldOffsets)[i] = 0;
                    fieldSet[i]        = ergba;
                    break;
                case 's':
                    (*fieldOffsets)[i] = 0;
                    fieldSet[i]        = estpq;
                    break;
                case 'y':
                    (*fieldOffsets)[i] = 1;
                    fieldSet[i]        = exyzw;
                    break;
                case 'g':
                    (*fieldOffsets)[i] = 1;
                    fieldSet[i]        = ergba;
                    break;
                case 't':
                    (*fieldOffsets)[i] = 1;
                    fieldSet[i]        = estpq;
                    break;
                case 'z':
                    (*fieldOffsets)[i] = 2;
                    fieldSet[i]        = exyzw;
                    break;
                case 'b':
                    (*fieldOffsets)[i] = 2;
                    fieldSet[i]        = ergba;
                    break;
                case 'p':
                    (*fieldOffsets)[i] = 2;
                    fieldSet[i]        = estpq;
                    break;
    
                case 'w':
                    (*fieldOffsets)[i] = 3;
                    fieldSet[i]        = exyzw;
                    break;
                case 'a':
                    (*fieldOffsets)[i] = 3;
                    fieldSet[i]        = ergba;
                    break;
                case 'q':
                    (*fieldOffsets)[i] = 3;
                    fieldSet[i]        = estpq;
                    break;
                default:
                    error(line, "illegal vector field selection", compString);
                    return false;
            }
        }
    
        for (unsigned int i = 0u; i < fieldOffsets->size(); ++i)
        {
            if ((*fieldOffsets)[i] >= vecSize)
            {
                error(line, "vector field selection out of range", compString);
                return false;
            }
    
            if (i > 0)
            {
                if (fieldSet[i] != fieldSet[i - 1])
                {
                    error(line, "illegal - vector component fields not from the same set", compString);
                    return false;
                }
            }
        }
    
        return true;
    }
    
    ///////////////////////////////////////////////////////////////////////
    //
    // Errors
    //
    ////////////////////////////////////////////////////////////////////////
    
    //
    // Used by flex/bison to output all syntax and parsing errors.
    //
    void TParseContext::error(const TSourceLoc &loc, const char *reason, const char *token)
    {
        mDiagnostics->error(loc, reason, token);
    }
    
    void TParseContext::error(const TSourceLoc &loc, const char *reason, const ImmutableString &token)
    {
        mDiagnostics->error(loc, reason, token.data());
    }
    
    void TParseContext::warning(const TSourceLoc &loc, const char *reason, const char *token)
    {
        mDiagnostics->warning(loc, reason, token);
    }
    
    void TParseContext::errorIfPLSDeclared(const TSourceLoc &loc, PLSIllegalOperations op)
    {
        if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
        {
            return;
        }
        if (mPLSFormats.empty())
        {
            // No pixel local storage uniforms have been declared yet. Remember this potential error in
            // case PLS gets declared later.
            mPLSPotentialErrors.emplace_back(loc, op);
            return;
        }
        switch (op)
        {
            case PLSIllegalOperations::Discard:
                error(loc, "illegal discard when pixel local storage is declared", "discard");
                break;
            case PLSIllegalOperations::ReturnFromMain:
                error(loc, "illegal return from main when pixel local storage is declared", "return");
                break;
            case PLSIllegalOperations::AssignFragDepth:
                error(loc, "value not assignable when pixel local storage is declared", "gl_FragDepth");
                break;
            case PLSIllegalOperations::AssignSampleMask:
                error(loc, "value not assignable when pixel local storage is declared",
                      "gl_SampleMask");
                break;
            case PLSIllegalOperations::FragDataIndexNonzero:
                error(loc, "illegal nonzero index qualifier when pixel local storage is declared",
                      "layout");
                break;
            case PLSIllegalOperations::EnableAdvancedBlendEquation:
                error(loc, "illegal advanced blend equation when pixel local storage is declared",
                      "layout");
                break;
        }
    }
    
    void TParseContext::outOfRangeError(bool isError,
                                        const TSourceLoc &loc,
                                        const char *reason,
                                        const char *token)
    {
        if (isError)
        {
            error(loc, reason, token);
        }
        else
        {
            warning(loc, reason, token);
        }
    }
    
    void TParseContext::setTreeRoot(TIntermBlock *treeRoot)
    {
        mTreeRoot = treeRoot;
        mTreeRoot->setIsTreeRoot();
    }
    
    //
    // Same error message for all places assignments don't work.
    //
    void TParseContext::assignError(const TSourceLoc &line,
                                    const char *op,
                                    const TType &left,
                                    const TType &right)
    {
        TInfoSinkBase reasonStream;
        reasonStream << "cannot convert from '" << right << "' to '" << left << "'";
        error(line, reasonStream.c_str(), op);
    }
    
    //
    // Same error message for all places unary operations don't work.
    //
    void TParseContext::unaryOpError(const TSourceLoc &line, const char *op, const TType &operand)
    {
        TInfoSinkBase reasonStream;
        reasonStream << "wrong operand type - no operation '" << op
                     << "' exists that takes an operand of type " << operand
                     << " (or there is no acceptable conversion)";
        error(line, reasonStream.c_str(), op);
    }
    
    //
    // Same error message for all binary operations don't work.
    //
    void TParseContext::binaryOpError(const TSourceLoc &line,
                                      const char *op,
                                      const TType &left,
                                      const TType &right)
    {
        TInfoSinkBase reasonStream;
        reasonStream << "wrong operand types - no operation '" << op
                     << "' exists that takes a left-hand operand of type '" << left
                     << "' and a right operand of type '" << right
                     << "' (or there is no acceptable conversion)";
        error(line, reasonStream.c_str(), op);
    }
    
    void TParseContext::checkPrecisionSpecified(const TSourceLoc &line,
                                                TPrecision precision,
                                                TBasicType type)
    {
        if (precision != EbpUndefined && !SupportsPrecision(type))
        {
            error(line, "illegal type for precision qualifier", getBasicString(type));
        }
    
        if (precision == EbpUndefined)
        {
            switch (type)
            {
                case EbtFloat:
                    error(line, "No precision specified for (float)", "");
                    return;
                case EbtInt:
                case EbtUInt:
                    UNREACHABLE();  // there's always a predeclared qualifier
                    error(line, "No precision specified (int)", "");
                    return;
                default:
                    if (IsOpaqueType(type))
                    {
                        error(line, "No precision specified", getBasicString(type));
                        return;
                    }
            }
        }
    }
    
    void TParseContext::markStaticReadIfSymbol(TIntermNode *node)
    {
        TIntermSwizzle *swizzleNode = node->getAsSwizzleNode();
        if (swizzleNode)
        {
            markStaticReadIfSymbol(swizzleNode->getOperand());
            return;
        }
        TIntermBinary *binaryNode = node->getAsBinaryNode();
        if (binaryNode)
        {
            switch (binaryNode->getOp())
            {
                case EOpIndexDirect:
                case EOpIndexIndirect:
                case EOpIndexDirectStruct:
                case EOpIndexDirectInterfaceBlock:
                    markStaticReadIfSymbol(binaryNode->getLeft());
                    return;
                default:
                    return;
            }
        }
        TIntermSymbol *symbolNode = node->getAsSymbolNode();
        if (symbolNode)
        {
            symbolTable.markStaticRead(symbolNode->variable());
        }
    }
    
    // Both test and if necessary, spit out an error, to see if the node is really
    // an l-value that can be operated on this way.
    bool TParseContext::checkCanBeLValue(const TSourceLoc &line, const char *op, TIntermTyped *node)
    {
        TIntermSwizzle *swizzleNode = node->getAsSwizzleNode();
        if (swizzleNode)
        {
            bool ok = checkCanBeLValue(line, op, swizzleNode->getOperand());
            if (ok && swizzleNode->hasDuplicateOffsets())
            {
                error(line, " l-value of swizzle cannot have duplicate components", op);
                return false;
            }
            return ok;
        }
    
        TIntermBinary *binaryNode = node->getAsBinaryNode();
        if (binaryNode)
        {
            switch (binaryNode->getOp())
            {
                case EOpIndexDirect:
                case EOpIndexIndirect:
                case EOpIndexDirectStruct:
                case EOpIndexDirectInterfaceBlock:
                    if (node->getMemoryQualifier().readonly)
                    {
                        error(line, "can't modify a readonly variable", op);
                        return false;
                    }
                    return checkCanBeLValue(line, op, binaryNode->getLeft());
                default:
                    break;
            }
            error(line, " l-value required", op);
            return false;
        }
    
        std::string message;
        switch (node->getQualifier())
        {
            case EvqConst:
                message = "can't modify a const";
                break;
            case EvqParamConst:
                message = "can't modify a const";
                break;
            case EvqAttribute:
                message = "can't modify an attribute";
                break;
            case EvqFragmentIn:
            case EvqVertexIn:
            case EvqGeometryIn:
            case EvqTessControlIn:
            case EvqTessEvaluationIn:
            case EvqSmoothIn:
            case EvqFlatIn:
            case EvqNoPerspectiveIn:
            case EvqCentroidIn:
            case EvqSampleIn:
            case EvqNoPerspectiveCentroidIn:
            case EvqNoPerspectiveSampleIn:
                message = "can't modify an input";
                break;
            case EvqUniform:
                message = "can't modify a uniform";
                break;
            case EvqVaryingIn:
                message = "can't modify a varying";
                break;
            case EvqFragCoord:
                message = "can't modify gl_FragCoord";
                break;
            case EvqFrontFacing:
                message = "can't modify gl_FrontFacing";
                break;
            case EvqHelperInvocation:
                message = "can't modify gl_HelperInvocation";
                break;
            case EvqPointCoord:
                message = "can't modify gl_PointCoord";
                break;
            case EvqNumWorkGroups:
                message = "can't modify gl_NumWorkGroups";
                break;
            case EvqWorkGroupSize:
                message = "can't modify gl_WorkGroupSize";
                break;
            case EvqWorkGroupID:
                message = "can't modify gl_WorkGroupID";
                break;
            case EvqLocalInvocationID:
                message = "can't modify gl_LocalInvocationID";
                break;
            case EvqGlobalInvocationID:
                message = "can't modify gl_GlobalInvocationID";
                break;
            case EvqLocalInvocationIndex:
                message = "can't modify gl_LocalInvocationIndex";
                break;
            case EvqViewIDOVR:
                message = "can't modify gl_ViewID_OVR";
                break;
            case EvqComputeIn:
                message = "can't modify work group size variable";
                break;
            case EvqPerVertexIn:
                message = "can't modify any member in gl_in";
                break;
            case EvqPrimitiveIDIn:
                message = "can't modify gl_PrimitiveIDIn";
                break;
            case EvqInvocationID:
                message = "can't modify gl_InvocationID";
                break;
            case EvqPrimitiveID:
                if (mShaderType == GL_FRAGMENT_SHADER)
                {
                    message = "can't modify gl_PrimitiveID in a fragment shader";
                }
                break;
            case EvqLayerIn:
                message = "can't modify gl_Layer in a fragment shader";
                break;
            case EvqShadingRateEXT:
                message = "can't modify gl_ShadingRateEXT";
                break;
            case EvqSampleID:
                message = "can't modify gl_SampleID";
                break;
            case EvqSampleMaskIn:
                message = "can't modify gl_SampleMaskIn";
                break;
            case EvqSamplePosition:
                message = "can't modify gl_SamplePosition";
                break;
            case EvqClipDistance:
                if (mShaderType == GL_FRAGMENT_SHADER)
                {
                    message = "can't modify gl_ClipDistance in a fragment shader";
                }
                break;
            case EvqCullDistance:
                if (mShaderType == GL_FRAGMENT_SHADER)
                {
                    message = "can't modify gl_CullDistance in a fragment shader";
                }
                break;
            case EvqFragDepth:
                errorIfPLSDeclared(line, PLSIllegalOperations::AssignFragDepth);
                break;
            case EvqSampleMask:
                errorIfPLSDeclared(line, PLSIllegalOperations::AssignSampleMask);
                break;
            default:
                //
                // Type that can't be written to?
                //
                if (node->getBasicType() == EbtVoid)
                {
                    message = "can't modify void";
                }
                if (IsOpaqueType(node->getBasicType()))
                {
                    message = "can't modify a variable with type ";
                    message += getBasicString(node->getBasicType());
                }
                else if (node->getMemoryQualifier().readonly)
                {
                    message = "can't modify a readonly variable";
                }
        }
    
        ASSERT(binaryNode == nullptr && swizzleNode == nullptr);
        TIntermSymbol *symNode = node->getAsSymbolNode();
        if (message.empty() && symNode != nullptr)
        {
            symbolTable.markStaticWrite(symNode->variable());
            return true;
        }
    
        std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
        reasonStream << "l-value required";
        if (!message.empty())
        {
            if (symNode)
            {
                // Symbol inside an expression can't be nameless.
                ASSERT(symNode->variable().symbolType() != SymbolType::Empty);
                const ImmutableString &symbol = symNode->getName();
                reasonStream << " (" << message << " \"" << symbol << "\")";
            }
            else
            {
                reasonStream << " (" << message << ")";
            }
        }
        std::string reason = reasonStream.str();
        error(line, reason.c_str(), op);
    
        return false;
    }
    
    // Both test, and if necessary spit out an error, to see if the node is really
    // a constant.
    void TParseContext::checkIsConst(TIntermTyped *node)
    {
        if (node->getQualifier() != EvqConst)
        {
            error(node->getLine(), "constant expression required", "");
        }
    }
    
    // Both test, and if necessary spit out an error, to see if the node is really
    // an integer.
    void TParseContext::checkIsScalarInteger(TIntermTyped *node, const char *token)
    {
        if (!node->isScalarInt())
        {
            error(node->getLine(), "integer expression required", token);
        }
    }
    
    // Both test, and if necessary spit out an error, to see if we are currently
    // globally scoped.
    bool TParseContext::checkIsAtGlobalLevel(const TSourceLoc &line, const char *token)
    {
        if (!symbolTable.atGlobalLevel())
        {
            error(line, "only allowed at global scope", token);
            return false;
        }
        return true;
    }
    
    void TParseContext::checkIsValidExpressionStatement(const TSourceLoc &line, TIntermTyped *expr)
    {
        if (expr->isInterfaceBlock())
        {
            error(line, "expression statement is not allowed for interface blocks", "");
        }
    }
    
    // ESSL 3.00.5 sections 3.8 and 3.9.
    // If it starts "gl_" or contains two consecutive underscores, it's reserved.
    // Also checks for "webgl_" and "_webgl_" reserved identifiers if parsing a webgl shader.
    bool TParseContext::checkIsNotReserved(const TSourceLoc &line, const ImmutableString &identifier)
    {
        static const char *reservedErrMsg = "reserved built-in name";
        if (gl::IsBuiltInName(identifier.data()))
        {
            error(line, reservedErrMsg, "gl_");
            return false;
        }
        if (sh::IsWebGLBasedSpec(mShaderSpec))
        {
            if (identifier.beginsWith("webgl_"))
            {
                error(line, reservedErrMsg, "webgl_");
                return false;
            }
            if (identifier.beginsWith("_webgl_"))
            {
                error(line, reservedErrMsg, "_webgl_");
                return false;
            }
        }
        if (identifier.contains("__"))
        {
            if (sh::IsWebGLBasedSpec(mShaderSpec))
            {
                error(line,
                      "identifiers containing two consecutive underscores (__) are reserved as "
                      "possible future keywords",
                      identifier);
                return false;
            }
            else
            {
                // Using double underscores is allowed, but may result in unintended behaviors, so a
                // warning is issued.
                // OpenGL ES Shader Language 3.2 specification:
                // > 3.7. Keywords
                // > ...
                // > In addition, all identifiers containing two consecutive underscores (__) are
                // > reserved for use by underlying software layers. Defining such a name in a shader
                // > does not itself result in an error, but may result in unintended behaviors that
                // > stem from having multiple definitions of the same name.
                warning(line,
                        "all identifiers containing two consecutive underscores (__) are reserved - "
                        "unintented behaviors are possible",
                        identifier.data());
            }
        }
        return true;
    }
    
    // Make sure the argument types are correct for constructing a specific type.
    bool TParseContext::checkConstructorArguments(const TSourceLoc &line,
                                                  const TIntermSequence &arguments,
                                                  const TType &type)
    {
        if (arguments.empty())
        {
            error(line, "constructor does not have any arguments", "constructor");
            return false;
        }
    
        for (TIntermNode *arg : arguments)
        {
            markStaticReadIfSymbol(arg);
            const TIntermTyped *argTyped = arg->getAsTyped();
            ASSERT(argTyped != nullptr);
            if (type.getBasicType() != EbtStruct && IsOpaqueType(argTyped->getBasicType()))
            {
                std::string reason("cannot convert a variable with type ");
                reason += getBasicString(argTyped->getBasicType());
                error(line, reason.c_str(), "constructor");
                return false;
            }
            else if (argTyped->getMemoryQualifier().writeonly)
            {
                error(line, "cannot convert a variable with writeonly", "constructor");
                return false;
            }
            if (argTyped->getBasicType() == EbtVoid)
            {
                error(line, "cannot convert a void", "constructor");
                return false;
            }
        }
    
        if (type.isArray())
        {
            // The size of an unsized constructor should already have been determined.
            ASSERT(!type.isUnsizedArray());
            if (static_cast<size_t>(type.getOutermostArraySize()) != arguments.size())
            {
                error(line, "array constructor needs one argument per array element", "constructor");
                return false;
            }
            // GLSL ES 3.00 section 5.4.4: Each argument must be the same type as the element type of
            // the array.
            for (TIntermNode *const &argNode : arguments)
            {
                const TType &argType = argNode->getAsTyped()->getType();
                if (mShaderVersion < 310 && argType.isArray())
                {
                    error(line, "constructing from a non-dereferenced array", "constructor");
                    return false;
                }
                if (!argType.isElementTypeOf(type))
                {
                    error(line, "Array constructor argument has an incorrect type", "constructor");
                    return false;
                }
            }
        }
        else if (type.getBasicType() == EbtStruct)
        {
            const TFieldList &fields = type.getStruct()->fields();
            if (fields.size() != arguments.size())
            {
                error(line,
                      "Number of constructor parameters does not match the number of structure fields",
                      "constructor");
                return false;
            }
    
            for (size_t i = 0; i < fields.size(); i++)
            {
                if (i >= arguments.size() ||
                    arguments[i]->getAsTyped()->getType() != *fields[i]->type())
                {
                    error(line, "Structure constructor arguments do not match structure fields",
                          "constructor");
                    return false;
                }
            }
        }
        else
        {
            // We're constructing a scalar, vector, or matrix.
    
            // Note: It's okay to have too many components available, but not okay to have unused
            // arguments. 'full' will go to true when enough args have been seen. If we loop again,
            // there is an extra argument, so 'overFull' will become true.
    
            size_t size    = 0;
            bool full      = false;
            bool overFull  = false;
            bool matrixArg = false;
            for (TIntermNode *arg : arguments)
            {
                const TIntermTyped *argTyped = arg->getAsTyped();
                ASSERT(argTyped != nullptr);
    
                if (argTyped->getBasicType() == EbtStruct)
                {
                    error(line, "a struct cannot be used as a constructor argument for this type",
                          "constructor");
                    return false;
                }
                if (argTyped->getBasicType() == EbtInterfaceBlock)
                {
                    error(line,
                          "an interface block cannot be used as a constructor argument for this type",
                          "constructor");
                    return false;
                }
                if (argTyped->getType().isArray())
                {
                    error(line, "constructing from a non-dereferenced array", "constructor");
                    return false;
                }
                if (argTyped->getType().isMatrix())
                {
                    matrixArg = true;
                }
    
                size += argTyped->getType().getObjectSize();
                if (full)
                {
                    overFull = true;
                }
                if (size >= type.getObjectSize())
                {
                    full = true;
                }
            }
    
            if (type.isMatrix() && matrixArg)
            {
                if (arguments.size() != 1)
                {
                    error(line, "constructing matrix from matrix can only take one argument",
                          "constructor");
                    return false;
                }
            }
            else
            {
                if (size != 1 && size < type.getObjectSize())
                {
                    error(line, "not enough data provided for construction", "constructor");
                    return false;
                }
                if (overFull)
                {
                    error(line, "too many arguments", "constructor");
                    return false;
                }
            }
        }
    
        return true;
    }
    
    // This function checks to see if a void variable has been declared and raise an error message for
    // such a case
    //
    // returns true in case of an error
    //
    bool TParseContext::checkIsNonVoid(const TSourceLoc &line,
                                       const ImmutableString &identifier,
                                       const TBasicType &type)
    {
        if (type == EbtVoid)
        {
            error(line, "illegal use of type 'void'", identifier);
            return false;
        }
    
        return true;
    }
    
    // This function checks to see if the node (for the expression) contains a scalar boolean expression
    // or not.
    bool TParseContext::checkIsScalarBool(const TSourceLoc &line, const TIntermTyped *type)
    {
        if (type->getBasicType() != EbtBool || !type->isScalar())
        {
            error(line, "boolean expression expected", "");
            return false;
        }
        return true;
    }
    
    // This function checks to see if the node (for the expression) contains a scalar boolean expression
    // or not.
    void TParseContext::checkIsScalarBool(const TSourceLoc &line, const TPublicType &pType)
    {
        if (pType.getBasicType() != EbtBool || pType.isAggregate())
        {
            error(line, "boolean expression expected", "");
        }
    }
    
    bool TParseContext::checkIsNotOpaqueType(const TSourceLoc &line,
                                             const TTypeSpecifierNonArray &pType,
                                             const char *reason)
    {
        if (pType.type == EbtStruct)
        {
            if (ContainsOpaque<IsSamplerFunc>(pType.userDef))
            {
                std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
                reasonStream << reason << " (structure contains a sampler)";
                std::string reasonStr = reasonStream.str();
                error(line, reasonStr.c_str(), getBasicString(pType.type));
                return false;
            }
            // only samplers need to be checked from structs, since other opaque types can't be struct
            // members.
            return true;
        }
        else if (IsOpaqueType(pType.type))
        {
            error(line, reason, getBasicString(pType.type));
            return false;
        }
    
        return true;
    }
    
    void TParseContext::checkDeclaratorLocationIsNotSpecified(const TSourceLoc &line,
                                                              const TPublicType &pType)
    {
        if (pType.layoutQualifier.location != -1)
        {
            error(line, "location must only be specified for a single input or output variable",
                  "location");
        }
    }
    
    void TParseContext::checkLocationIsNotSpecified(const TSourceLoc &location,
                                                    const TLayoutQualifier &layoutQualifier)
    {
        if (layoutQualifier.location != -1)
        {
            const char *errorMsg = "invalid layout qualifier: only valid on program inputs and outputs";
            if (mShaderVersion >= 310)
            {
                errorMsg =
                    "invalid layout qualifier: only valid on shader inputs, outputs, and uniforms";
            }
            error(location, errorMsg, "location");
        }
    }
    
    void TParseContext::checkStd430IsForShaderStorageBlock(const TSourceLoc &location,
                                                           const TLayoutBlockStorage &blockStorage,
                                                           const TQualifier &qualifier)
    {
        if (blockStorage == EbsStd430 && qualifier != EvqBuffer)
        {
            error(location, "The std430 layout is supported only for shader storage blocks.", "std430");
        }
    }
    
    // Do size checking for an array type's size.
    unsigned int TParseContext::checkIsValidArraySize(const TSourceLoc &line, TIntermTyped *expr)
    {
        TIntermConstantUnion *constant = expr->getAsConstantUnion();
    
        // ANGLE should be able to fold any EvqConst expressions resulting in an integer - but to be
        // safe against corner cases we still check for constant folding. Some interpretations of the
        // spec have allowed constant expressions with side effects - like array length() method on a
        // non-constant array.
        if (expr->getQualifier() != EvqConst || constant == nullptr || !constant->isScalarInt())
        {
            error(line, "array size must be a constant integer expression", "");
            return 1u;
        }
    
        unsigned int size = 0u;
    
        if (constant->getBasicType() == EbtUInt)
        {
            size = constant->getUConst(0);
        }
        else
        {
            int signedSize = constant->getIConst(0);
    
            if (signedSize < 0)
            {
                error(line, "array size must be non-negative", "");
                return 1u;
            }
    
            size = static_cast<unsigned int>(signedSize);
        }
    
        if (size == 0u)
        {
            error(line, "array size must be greater than zero", "");
            return 1u;
        }
    
        if (IsOutputHLSL(getOutputType()))
        {
            // The size of arrays is restricted here to prevent issues further down the
            // compiler/translator/driver stack. Shader Model 5 generation hardware is limited to
            // 4096 registers so this should be reasonable even for aggressively optimizable code.
            const unsigned int sizeLimit = 65536;
    
            if (size > sizeLimit)
            {
                error(line, "array size too large", "");
                return 1u;
            }
        }
    
        return size;
    }
    
    bool TParseContext::checkIsValidArrayDimension(const TSourceLoc &line,
                                                   TVector<unsigned int> *arraySizes)
    {
        if (arraySizes->size() > mMaxExpressionComplexity)
        {
            error(line, "array has too many dimensions", "");
            return false;
        }
        return true;
    }
    
    // See if this qualifier can be an array.
    bool TParseContext::checkIsValidQualifierForArray(const TSourceLoc &line,
                                                      const TPublicType &elementQualifier)
    {
        if ((elementQualifier.qualifier == EvqAttribute) ||
            (elementQualifier.qualifier == EvqVertexIn) ||
            (elementQualifier.qualifier == EvqConst && mShaderVersion < 300))
        {
            error(line, "cannot declare arrays of this qualifier",
                  TType(elementQualifier).getQualifierString());
            return false;
        }
    
        return true;
    }
    
    // See if this element type can be formed into an array.
    bool TParseContext::checkArrayElementIsNotArray(const TSourceLoc &line,
                                                    const TPublicType &elementType)
    {
        if (mShaderVersion < 310 && elementType.isArray())
        {
            TInfoSinkBase typeString;
            typeString << TType(elementType);
            error(line, "cannot declare arrays of arrays", typeString.c_str());
            return false;
        }
        return true;
    }
    
    // Check for array-of-arrays being used as non-allowed shader inputs/outputs.
    bool TParseContext::checkArrayOfArraysInOut(const TSourceLoc &line,
                                                const TPublicType &elementType,
                                                const TType &arrayType)
    {
        if (arrayType.isArrayOfArrays())
        {
            if (elementType.qualifier == EvqVertexOut)
            {
                error(line, "vertex shader output cannot be an array of arrays",
                      TType(elementType).getQualifierString());
                return false;
            }
            if (elementType.qualifier == EvqFragmentIn)
            {
                error(line, "fragment shader input cannot be an array of arrays",
                      TType(elementType).getQualifierString());
                return false;
            }
            if (elementType.qualifier == EvqFragmentOut || elementType.qualifier == EvqFragmentInOut)
            {
                error(line, "fragment shader output cannot be an array of arrays",
                      TType(elementType).getQualifierString());
                return false;
            }
        }
        return true;
    }
    
    // Check if this qualified element type can be formed into an array. This is only called when array
    // brackets are associated with an identifier in a declaration, like this:
    //   float a[2];
    // Similar checks are done in addFullySpecifiedType for array declarations where the array brackets
    // are associated with the type, like this:
    //   float[2] a;
    bool TParseContext::checkIsValidTypeAndQualifierForArray(const TSourceLoc &indexLocation,
                                                             const TPublicType &elementType)
    {
        if (!checkArrayElementIsNotArray(indexLocation, elementType))
        {
            return false;
        }
        // In ESSL1.00 shaders, structs cannot be varying (section 4.3.5). This is checked elsewhere.
        // In ESSL3.00 shaders, struct inputs/outputs are allowed but not arrays of structs (section
        // 4.3.4).
        // Geometry shader requires each user-defined input be declared as arrays or inside input
        // blocks declared as arrays (GL_EXT_geometry_shader section 11.1gs.4.3). For the purposes of
        // interface matching, such variables and blocks are treated as though they were not declared
        // as arrays (GL_EXT_geometry_shader section 7.4.1).
        if (mShaderVersion >= 300 && elementType.getBasicType() == EbtStruct &&
            sh::IsVarying(elementType.qualifier) &&
            !IsGeometryShaderInput(mShaderType, elementType.qualifier) &&
            !IsTessellationControlShaderInput(mShaderType, elementType.qualifier) &&
            !IsTessellationEvaluationShaderInput(mShaderType, elementType.qualifier) &&
            !IsTessellationControlShaderOutput(mShaderType, elementType.qualifier))
        {
            TInfoSinkBase typeString;
            typeString << TType(elementType);
            error(indexLocation, "cannot declare arrays of structs of this qualifier",
                  typeString.c_str());
            return false;
        }
        return checkIsValidQualifierForArray(indexLocation, elementType);
    }
    
    void TParseContext::checkNestingLevel(const TSourceLoc &line)
    {
        if (static_cast<size_t>(mLoopNestingLevel + mSwitchNestingLevel) > mMaxStatementDepth)
        {
            error(line, "statement is too deeply nested", "");
        }
    }
    
    // Enforce non-initializer type/qualifier rules.
    void TParseContext::checkCanBeDeclaredWithoutInitializer(const TSourceLoc &line,
                                                             const ImmutableString &identifier,
                                                             TType *type)
    {
        ASSERT(type != nullptr);
        if (type->getQualifier() == EvqConst)
        {
            // Make the qualifier make sense.
            type->setQualifier(EvqTemporary);
    
            // Generate informative error messages for ESSL1.
            // In ESSL3 arrays and structures containing arrays can be constant.
            if (mShaderVersion < 300 && type->isStructureContainingArrays())
            {
                error(line,
                      "structures containing arrays may not be declared constant since they cannot be "
                      "initialized",
                      identifier);
            }
            else
            {
                error(line, "variables with qualifier 'const' must be initialized", identifier);
            }
        }
    }
    
    void TParseContext::checkDeclarationIsValidArraySize(const TSourceLoc &line,
                                                         const ImmutableString &identifier,
                                                         TType *type)
    {
    
        // Implicitly declared arrays are only allowed with tessellation or geometry shader inputs
        if (type->isUnsizedArray() &&
            ((mShaderType != GL_TESS_CONTROL_SHADER && mShaderType != GL_TESS_EVALUATION_SHADER &&
              mShaderType != GL_GEOMETRY_SHADER) ||
             (mShaderType == GL_GEOMETRY_SHADER && type->getQualifier() == EvqGeometryOut)))
        {
            error(line,
                  "implicitly sized arrays only allowed for tessellation shaders "
                  "or geometry shader inputs",
                  identifier);
        }
    }
    
    // Do some simple checks that are shared between all variable declarations,
    // and update the symbol table.
    //
    // Returns true if declaring the variable succeeded.
    //
    bool TParseContext::declareVariable(const TSourceLoc &line,
                                        const ImmutableString &identifier,
                                        const TType *type,
                                        TVariable **variable)
    {
        ASSERT((*variable) == nullptr);
    
        SymbolType symbolType = SymbolType::UserDefined;
        switch (type->getQualifier())
        {
            case EvqClipDistance:
            case EvqCullDistance:
            case EvqFragDepth:
            case EvqLastFragData:
            case EvqLastFragColor:
            case EvqLastFragDepth:
            case EvqLastFragStencil:
                symbolType = SymbolType::BuiltIn;
                break;
            default:
                break;
        }
    
        (*variable) = new TVariable(&symbolTable, identifier, type, symbolType);
    
        if (type->getQualifier() == EvqFragmentOut)
        {
            if (type->getLayoutQualifier().index != -1 && type->getLayoutQualifier().location == -1)
            {
                error(line,
                      "If index layout qualifier is specified for a fragment output, location must "
                      "also be specified.",
                      "index");
                return false;
            }
        }
        else
        {
            checkIndexIsNotSpecified(line, type->getLayoutQualifier().index);
        }
    
        if (!((identifier.beginsWith("gl_LastFragData") || type->getQualifier() == EvqFragmentInOut) &&
              (isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch) ||
               isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch_non_coherent))))
        {
            checkNoncoherentIsNotSpecified(line, type->getLayoutQualifier().noncoherent);
        }
        else if (isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch_non_coherent) &&
                 !isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch))
        {
            checkNoncoherentIsSpecified(line, type->getLayoutQualifier().noncoherent);
        }
    
        checkBindingIsValid(line, *type);
    
        bool needsReservedCheck = true;
    
        // gl_LastFragData may be redeclared with a new precision qualifier
        if (type->isArray() && identifier.beginsWith("gl_LastFragData"))
        {
            const TVariable *maxDrawBuffers = static_cast<const TVariable *>(
                symbolTable.findBuiltIn(ImmutableString("gl_MaxDrawBuffers"), mShaderVersion));
            if (type->isArrayOfArrays())
            {
                error(line, "redeclaration of gl_LastFragData as an array of arrays", identifier);
                return false;
            }
            else if (static_cast<int>(type->getOutermostArraySize()) ==
                     maxDrawBuffers->getConstPointer()->getIConst())
            {
                if (const TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion))
                {
                    needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
                }
            }
            else
            {
                error(line, "redeclaration of gl_LastFragData with size != gl_MaxDrawBuffers",
                      identifier);
                return false;
            }
        }
        else if (identifier.beginsWith("gl_LastFragColorARM") ||
                 identifier.beginsWith("gl_LastFragDepthARM") ||
                 identifier.beginsWith("gl_LastFragStencilARM"))
        {
            // gl_LastFrag{Color,Depth,Stencil}ARM may be redeclared with a new precision qualifier
            if (const TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion))
            {
                needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
            }
        }
        else if (type->isArray() && identifier == "gl_ClipDistance")
        {
            // gl_ClipDistance can be redeclared with smaller size than gl_MaxClipDistances
            const TVariable *maxClipDistances = static_cast<const TVariable *>(
                symbolTable.findBuiltIn(ImmutableString("gl_MaxClipDistances"), mShaderVersion));
            if (!maxClipDistances)
            {
                // Unsupported extension
                needsReservedCheck = true;
            }
            else if (type->isArrayOfArrays())
            {
                error(line, "redeclaration of gl_ClipDistance as an array of arrays", identifier);
                return false;
            }
            else if (static_cast<int>(type->getOutermostArraySize()) <=
                     maxClipDistances->getConstPointer()->getIConst())
            {
                const TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion);
                if (builtInSymbol)
                {
                    needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
                }
            }
            else
            {
                error(line, "redeclaration of gl_ClipDistance with size > gl_MaxClipDistances",
                      identifier);
                return false;
            }
        }
        else if (type->isArray() && identifier == "gl_CullDistance")
        {
            // gl_CullDistance can be redeclared with smaller size than gl_MaxCullDistances
            const TVariable *maxCullDistances = static_cast<const TVariable *>(
                symbolTable.findBuiltIn(ImmutableString("gl_MaxCullDistances"), mShaderVersion));
            if (!maxCullDistances)
            {
                // Unsupported extension
                needsReservedCheck = true;
            }
            else if (type->isArrayOfArrays())
            {
                error(line, "redeclaration of gl_CullDistance as an array of arrays", identifier);
                return false;
            }
            else if (static_cast<int>(type->getOutermostArraySize()) <=
                     maxCullDistances->getConstPointer()->getIConst())
            {
                if (const TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion))
                {
                    needsReservedCheck = !checkCanUseOneOfExtensions(line, builtInSymbol->extensions());
                }
            }
            else
            {
                error(line, "redeclaration of gl_CullDistance with size > gl_MaxCullDistances",
                      identifier);
                return false;
            }
        }
        else if (isExtensionEnabled(TExtension::EXT_conservative_depth) &&
                 mShaderType == GL_FRAGMENT_SHADER && identifier == "gl_FragDepth")
        {
            if (type->getBasicType() != EbtFloat || type->getNominalSize() != 1 ||
                type->getSecondarySize() != 1 || type->isArray())
            {
                error(line, "gl_FragDepth can only be redeclared as float", identifier);
                return false;
            }
            needsReservedCheck = (symbolType == SymbolType::UserDefined);
        }
        else if (isExtensionEnabled(TExtension::EXT_separate_shader_objects) &&
                 mShaderType == GL_VERTEX_SHADER)
        {
            bool isRedefiningPositionOrPointSize = false;
            if (identifier == "gl_Position")
            {
                if (type->getBasicType() != EbtFloat || type->getNominalSize() != 4 ||
                    type->getSecondarySize() != 1 || type->isArray())
                {
                    error(line, "gl_Position can only be redeclared as vec4", identifier);
                    return false;
                }
                needsReservedCheck                         = false;
                mPositionRedeclaredForSeparateShaderObject = true;
                isRedefiningPositionOrPointSize            = true;
            }
            else if (identifier == "gl_PointSize")
            {
                if (type->getBasicType() != EbtFloat || type->getNominalSize() != 1 ||
                    type->getSecondarySize() != 1 || type->isArray())
                {
                    error(line, "gl_PointSize can only be redeclared as float", identifier);
                    return false;
                }
                needsReservedCheck                          = false;
                mPointSizeRedeclaredForSeparateShaderObject = true;
                isRedefiningPositionOrPointSize             = true;
            }
            if (isRedefiningPositionOrPointSize && mPositionOrPointSizeUsedForSeparateShaderObject)
            {
                error(line,
                      "When EXT_separate_shader_objects is enabled, both gl_Position and "
                      "gl_PointSize must be redeclared before either is used",
                      identifier);
            }
        }
    
        if (needsReservedCheck && !checkIsNotReserved(line, identifier))
            return false;
    
        if (!symbolTable.declare(*variable))
        {
            error(line, "redefinition", identifier);
            return false;
        }
    
        if (!checkIsNonVoid(line, identifier, type->getBasicType()))
            return false;
    
        return true;
    }
    
    void TParseContext::parseParameterQualifier(const TSourceLoc &line,
                                                const TTypeQualifierBuilder &typeQualifierBuilder,
                                                TPublicType &type)
    {
        // The only parameter qualifiers a parameter can have are in, out, inout or const.
        TTypeQualifier typeQualifier =
            typeQualifierBuilder.getParameterTypeQualifier(type.getBasicType(), mDiagnostics);
    
        if (typeQualifier.qualifier == EvqParamOut || typeQualifier.qualifier == EvqParamInOut)
        {
            if (IsOpaqueType(type.getBasicType()))
            {
                error(line, "opaque types cannot be output parameters", type.getBasicString());
            }
        }
    
        if (!IsImage(type.getBasicType()))
        {
            checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, line);
        }
        else
        {
            type.setMemoryQualifier(typeQualifier.memoryQualifier);
        }
    
        type.setQualifier(typeQualifier.qualifier);
    
        if (typeQualifier.precision != EbpUndefined)
        {
            type.setPrecision(typeQualifier.precision);
        }
    
        if (typeQualifier.precise)
        {
            type.setPrecise(true);
        }
    }
    
    template <size_t size>
    bool TParseContext::checkCanUseOneOfExtensions(const TSourceLoc &line,
                                                   const std::array<TExtension, size> &extensions)
    {
        ASSERT(!extensions.empty());
        const TExtensionBehavior &extBehavior = extensionBehavior();
    
        bool canUseWithWarning    = false;
        bool canUseWithoutWarning = false;
    
        const char *errorMsgString   = "";
        TExtension errorMsgExtension = TExtension::UNDEFINED;
    
        for (TExtension extension : extensions)
        {
            auto extIter = extBehavior.find(extension);
            if (canUseWithWarning)
            {
                // We already have an extension that we can use, but with a warning.
                // See if we can use the alternative extension without a warning.
                if (extIter == extBehavior.end())
                {
                    continue;
                }
                if (extIter->second == EBhEnable || extIter->second == EBhRequire)
                {
                    canUseWithoutWarning = true;
                    break;
                }
                continue;
            }
            if (extension == TExtension::UNDEFINED)
            {
                continue;
            }
            else if (extIter == extBehavior.end())
            {
                errorMsgString    = "extension is not supported";
                errorMsgExtension = extension;
            }
            else if (extIter->second == EBhUndefined || extIter->second == EBhDisable)
            {
                errorMsgString    = "extension is disabled";
                errorMsgExtension = extension;
            }
            else if (extIter->second == EBhWarn)
            {
                errorMsgExtension = extension;
                canUseWithWarning = true;
            }
            else
            {
                ASSERT(extIter->second == EBhEnable || extIter->second == EBhRequire);
                canUseWithoutWarning = true;
                break;
            }
        }
    
        if (canUseWithoutWarning)
        {
            return true;
        }
        if (canUseWithWarning)
        {
            warning(line, "extension is being used", GetExtensionNameString(errorMsgExtension));
            return true;
        }
        error(line, errorMsgString, GetExtensionNameString(errorMsgExtension));
        return false;
    }
    
    template bool TParseContext::checkCanUseOneOfExtensions(
        const TSourceLoc &line,
        const std::array<TExtension, 1> &extensions);
    template bool TParseContext::checkCanUseOneOfExtensions(
        const TSourceLoc &line,
        const std::array<TExtension, 2> &extensions);
    template bool TParseContext::checkCanUseOneOfExtensions(
        const TSourceLoc &line,
        const std::array<TExtension, 3> &extensions);
    
    bool TParseContext::checkCanUseExtension(const TSourceLoc &line, TExtension extension)
    {
        ASSERT(extension != TExtension::UNDEFINED);
        return checkCanUseOneOfExtensions(line, std::array<TExtension, 1u>{{extension}});
    }
    
    // ESSL 3.00.6 section 4.8 Empty Declarations: "The combinations of qualifiers that cause
    // compile-time or link-time errors are the same whether or not the declaration is empty".
    // This function implements all the checks that are done on qualifiers regardless of if the
    // declaration is empty.
    void TParseContext::declarationQualifierErrorCheck(const sh::TQualifier qualifier,
                                                       const sh::TLayoutQualifier &layoutQualifier,
                                                       const TSourceLoc &location)
    {
        if (qualifier == EvqShared && !layoutQualifier.isEmpty())
        {
            error(location, "Shared memory declarations cannot have layout specified", "layout");
        }
    
        if (layoutQualifier.matrixPacking != EmpUnspecified)
        {
            error(location, "layout qualifier only valid for interface blocks",
                  getMatrixPackingString(layoutQualifier.matrixPacking));
            return;
        }
    
        if (layoutQualifier.blockStorage != EbsUnspecified)
        {
            error(location, "layout qualifier only valid for interface blocks",
                  getBlockStorageString(layoutQualifier.blockStorage));
            return;
        }
    
        if (qualifier != EvqFragDepth)
        {
            checkDepthIsNotSpecified(location, layoutQualifier.depth);
        }
    
        if (qualifier == EvqFragmentOut)
        {
            if (layoutQualifier.location != -1 && layoutQualifier.yuv == true)
            {
                error(location, "invalid layout qualifier combination", "yuv");
                return;
            }
        }
        else
        {
            checkYuvIsNotSpecified(location, layoutQualifier.yuv);
        }
    
        if (qualifier != EvqFragmentIn)
        {
            checkEarlyFragmentTestsIsNotSpecified(location, layoutQualifier.earlyFragmentTests);
        }
    
        // If multiview extension is enabled, "in" qualifier is allowed in the vertex shader in previous
        // parsing steps. So it needs to be checked here.
        if (anyMultiviewExtensionAvailable() && mShaderVersion < 300 && qualifier == EvqVertexIn)
        {
            error(location, "storage qualifier supported in GLSL ES 3.00 and above only", "in");
        }
    
        bool canHaveLocation = qualifier == EvqVertexIn || qualifier == EvqFragmentOut;
        if (mShaderVersion >= 300 &&
            (isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch) ||
             isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch_non_coherent)))
        {
            // In the case of EXT_shader_framebuffer_fetch or EXT_shader_framebuffer_fetch_non_coherent
            // extension, the location of inout qualifier is used to set the input attachment index
            canHaveLocation = canHaveLocation || qualifier == EvqFragmentInOut;
        }
        if (mShaderVersion >= 310)
        {
            canHaveLocation = canHaveLocation || qualifier == EvqUniform || IsVarying(qualifier);
            // We're not checking whether the uniform location is in range here since that depends on
            // the type of the variable.
            // The type can only be fully determined for non-empty declarations.
        }
        if (!canHaveLocation)
        {
            checkLocationIsNotSpecified(location, layoutQualifier);
        }
    }
    
    void TParseContext::atomicCounterQualifierErrorCheck(const TPublicType &publicType,
                                                         const TSourceLoc &location)
    {
        if (publicType.precision != EbpHigh)
        {
            error(location, "Can only be highp", "atomic counter");
        }
        // dEQP enforces compile error if location is specified. See uniform_location.test.
        if (publicType.layoutQualifier.location != -1)
        {
            error(location, "location must not be set for atomic_uint", "layout");
        }
        if (publicType.layoutQualifier.binding == -1)
        {
            error(location, "no binding specified", "atomic counter");
        }
    }
    
    void TParseContext::emptyDeclarationErrorCheck(const TType &type, const TSourceLoc &location)
    {
        if (type.isUnsizedArray())
        {
            // ESSL3 spec section 4.1.9: Array declaration which leaves the size unspecified is an
            // error. It is assumed that this applies to empty declarations as well.
            error(location, "empty array declaration needs to specify a size", "");
        }
    
        if (type.getQualifier() != EvqFragmentOut)
        {
            checkIndexIsNotSpecified(location, type.getLayoutQualifier().index);
        }
    }
    
    // These checks are done for all declarations that are non-empty. They're done for non-empty
    // declarations starting a declarator list, and declarators that follow an empty declaration.
    void TParseContext::nonEmptyDeclarationErrorCheck(const TPublicType &publicType,
                                                      const TSourceLoc &identifierLocation)
    {
        switch (publicType.qualifier)
        {
            case EvqVaryingIn:
            case EvqVaryingOut:
            case EvqAttribute:
            case EvqVertexIn:
            case EvqFragmentOut:
            case EvqFragmentInOut:
            case EvqComputeIn:
                if (publicType.getBasicType() == EbtStruct)
                {
                    error(identifierLocation, "cannot be used with a structure",
                          getQualifierString(publicType.qualifier));
                    return;
                }
                break;
            case EvqBuffer:
                if (publicType.getBasicType() != EbtInterfaceBlock)
                {
                    error(identifierLocation,
                          "cannot declare buffer variables at global scope(outside a block)",
                          getQualifierString(publicType.qualifier));
                    return;
                }
                break;
            default:
                break;
        }
        std::string reason(getBasicString(publicType.getBasicType()));
        reason += "s must be uniform";
        if (publicType.qualifier != EvqUniform &&
            !checkIsNotOpaqueType(identifierLocation, publicType.typeSpecifierNonArray, reason.c_str()))
        {
            return;
        }
    
        if ((publicType.qualifier != EvqTemporary && publicType.qualifier != EvqGlobal &&
             publicType.qualifier != EvqConst) &&
            publicType.getBasicType() == EbtYuvCscStandardEXT)
        {
            error(identifierLocation, "cannot be used with a yuvCscStandardEXT",
                  getQualifierString(publicType.qualifier));
            return;
        }
    
        if (mShaderVersion >= 310 && publicType.qualifier == EvqUniform)
        {
            // Valid uniform declarations can't be unsized arrays since uniforms can't be initialized.
            // But invalid shaders may still reach here with an unsized array declaration.
            TType type(publicType);
            if (!type.isUnsizedArray())
            {
                checkUniformLocationInRange(identifierLocation, type.getLocationCount(),
                                            publicType.layoutQualifier);
            }
        }
    
        if (mShaderVersion >= 300 && publicType.qualifier == EvqVertexIn)
        {
            // Valid vertex input declarations can't be unsized arrays since they can't be initialized.
            // But invalid shaders may still reach here with an unsized array declaration.
            TType type(publicType);
            if (!type.isUnsizedArray())
            {
                checkAttributeLocationInRange(identifierLocation, type.getLocationCount(),
                                              publicType.layoutQualifier);
            }
        }
    
        // check for layout qualifier issues
        const TLayoutQualifier layoutQualifier = publicType.layoutQualifier;
    
        if (IsImage(publicType.getBasicType()))
        {
    
            switch (layoutQualifier.imageInternalFormat)
            {
                case EiifRGBA32F:
                case EiifRGBA16F:
                case EiifR32F:
                case EiifRGBA8:
                case EiifRGBA8_SNORM:
                    if (!IsFloatImage(publicType.getBasicType()))
                    {
                        error(identifierLocation,
                              "internal image format requires a floating image type",
                              getBasicString(publicType.getBasicType()));
                        return;
                    }
                    break;
                case EiifRGBA32I:
                case EiifRGBA16I:
                case EiifRGBA8I:
                case EiifR32I:
                    if (!IsIntegerImage(publicType.getBasicType()))
                    {
                        error(identifierLocation,
                              "internal image format requires an integer image type",
                              getBasicString(publicType.getBasicType()));
                        return;
                    }
                    break;
                case EiifRGBA32UI:
                case EiifRGBA16UI:
                case EiifRGBA8UI:
                case EiifR32UI:
                    if (!IsUnsignedImage(publicType.getBasicType()))
                    {
                        error(identifierLocation,
                              "internal image format requires an unsigned image type",
                              getBasicString(publicType.getBasicType()));
                        return;
                    }
                    break;
                case EiifUnspecified:
                    error(identifierLocation, "layout qualifier", "No image internal format specified");
                    return;
                default:
                    error(identifierLocation, "layout qualifier", "unrecognized token");
                    return;
            }
    
            // GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers
            switch (layoutQualifier.imageInternalFormat)
            {
                case EiifR32F:
                case EiifR32I:
                case EiifR32UI:
                    break;
                default:
                    if (!publicType.memoryQualifier.readonly && !publicType.memoryQualifier.writeonly)
                    {
                        error(identifierLocation, "layout qualifier",
                              "Except for images with the r32f, r32i and r32ui format qualifiers, "
                              "image variables must be qualified readonly and/or writeonly");
                        return;
                    }
                    break;
            }
        }
        else if (IsPixelLocal(publicType.getBasicType()))
        {
            if (getShaderType() != GL_FRAGMENT_SHADER)
            {
                error(identifierLocation,
                      "undefined use of pixel local storage outside a fragment shader",
                      getBasicString(publicType.getBasicType()));
                return;
            }
            switch (layoutQualifier.imageInternalFormat)
            {
                case EiifR32F:
                case EiifRGBA8:
                    if (publicType.getBasicType() != EbtPixelLocalANGLE)
                    {
                        error(identifierLocation, "pixel local storage format requires pixelLocalANGLE",
                              getImageInternalFormatString(layoutQualifier.imageInternalFormat));
                    }
                    break;
                case EiifRGBA8I:
                    if (publicType.getBasicType() != EbtIPixelLocalANGLE)
                    {
                        error(identifierLocation,
                              "pixel local storage format requires ipixelLocalANGLE",
                              getImageInternalFormatString(layoutQualifier.imageInternalFormat));
                    }
                    break;
                case EiifR32UI:
                case EiifRGBA8UI:
                    if (publicType.getBasicType() != EbtUPixelLocalANGLE)
                    {
                        error(identifierLocation,
                              "pixel local storage format requires upixelLocalANGLE",
                              getImageInternalFormatString(layoutQualifier.imageInternalFormat));
                    }
                    break;
                case EiifR32I:
                case EiifRGBA8_SNORM:
                case EiifRGBA16F:
                case EiifRGBA32F:
                case EiifRGBA16I:
                case EiifRGBA32I:
                case EiifRGBA16UI:
                case EiifRGBA32UI:
                default:
                    ASSERT(!IsValidWithPixelLocalStorage(layoutQualifier.imageInternalFormat));
                    error(identifierLocation, "illegal pixel local storage format",
                          getImageInternalFormatString(layoutQualifier.imageInternalFormat));
                    break;
                case EiifUnspecified:
                    error(identifierLocation, "pixel local storage requires a format specifier",
                          "layout qualifier");
                    break;
            }
            checkMemoryQualifierIsNotSpecified(publicType.memoryQualifier, identifierLocation);
            checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
        }
        else
        {
            checkInternalFormatIsNotSpecified(identifierLocation, layoutQualifier.imageInternalFormat);
            checkMemoryQualifierIsNotSpecified(publicType.memoryQualifier, identifierLocation);
        }
    
        if (IsAtomicCounter(publicType.getBasicType()))
        {
            atomicCounterQualifierErrorCheck(publicType, identifierLocation);
        }
        else
        {
            checkOffsetIsNotSpecified(identifierLocation, layoutQualifier.offset);
        }
    }
    
    void TParseContext::checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type)
    {
        TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
        // Note that the ESSL 3.10 section 4.4.5 is not particularly clear on how the binding qualifier
        // on arrays of arrays should be handled. We interpret the spec so that the binding value is
        // incremented for each element of the innermost nested arrays. This is in line with how arrays
        // of arrays of blocks are specified to behave in GLSL 4.50 and a conservative interpretation
        // when it comes to which shaders are accepted by the compiler.
        int arrayTotalElementCount = type.getArraySizeProduct();
        if (IsPixelLocal(type.getBasicType()))
        {
            checkPixelLocalStorageBindingIsValid(identifierLocation, type);
        }
        else if (mShaderVersion < 310)
        {
            checkBindingIsNotSpecified(identifierLocation, layoutQualifier.binding);
        }
        else if (IsImage(type.getBasicType()))
        {
            checkImageBindingIsValid(identifierLocation, layoutQualifier.binding,
                                     arrayTotalElementCount);
        }
        else if (IsSampler(type.getBasicType()))
        {
            checkSamplerBindingIsValid(identifierLocation, layoutQualifier.binding,
                                       arrayTotalElementCount);
        }
        else if (IsAtomicCounter(type.getBasicType()))
        {
            checkAtomicCounterBindingIsValid(identifierLocation, layoutQualifier.binding);
        }
        else
        {
            ASSERT(!IsOpaqueType(type.getBasicType()));
            checkBindingIsNotSpecified(identifierLocation, layoutQualifier.binding);
        }
    }
    
    void TParseContext::checkCanUseLayoutQualifier(const TSourceLoc &location)
    {
        constexpr std::array<TExtension, 4u> extensions{
            {TExtension::EXT_shader_framebuffer_fetch,
             TExtension::EXT_shader_framebuffer_fetch_non_coherent,
             TExtension::KHR_blend_equation_advanced, TExtension::ANGLE_shader_pixel_local_storage}};
        if (getShaderVersion() < 300 && !checkCanUseOneOfExtensions(location, extensions))
        {
            error(location, "qualifier supported in GLSL ES 3.00 and above only", "layout");
        }
    }
    
    bool TParseContext::checkLayoutQualifierSupported(const TSourceLoc &location,
                                                      const ImmutableString &layoutQualifierName,
                                                      int versionRequired)
    {
    
        if (mShaderVersion < versionRequired)
        {
            error(location, "invalid layout qualifier: not supported", layoutQualifierName);
            return false;
        }
        return true;
    }
    
    bool TParseContext::checkWorkGroupSizeIsNotSpecified(const TSourceLoc &location,
                                                         const TLayoutQualifier &layoutQualifier)
    {
        const sh::WorkGroupSize &localSize = layoutQualifier.localSize;
        for (size_t i = 0u; i < localSize.size(); ++i)
        {
            if (localSize[i] != -1)
            {
                error(location,
                      "invalid layout qualifier: only valid when used with 'in' in a compute shader "
                      "global layout declaration",
                      getWorkGroupSizeString(i));
                return false;
            }
        }
    
        return true;
    }
    
    void TParseContext::checkInternalFormatIsNotSpecified(const TSourceLoc &location,
                                                          TLayoutImageInternalFormat internalFormat)
    {
        if (internalFormat != EiifUnspecified)
        {
            if (mShaderVersion < 310)
            {
                if (IsValidWithPixelLocalStorage(internalFormat))
                {
                    error(location,
                          "invalid layout qualifier: not supported before GLSL ES 3.10, except pixel "
                          "local storage",
                          getImageInternalFormatString(internalFormat));
                }
                else
                {
                    error(location, "invalid layout qualifier: not supported before GLSL ES 3.10",
                          getImageInternalFormatString(internalFormat));
                }
            }
            else
            {
                if (IsValidWithPixelLocalStorage(internalFormat))
                {
                    error(location,
                          "invalid layout qualifier: only valid when used with images or pixel local "
                          "storage ",
                          getImageInternalFormatString(internalFormat));
                }
                else
                {
                    error(location, "invalid layout qualifier: only valid when used with images",
                          getImageInternalFormatString(internalFormat));
                }
            }
        }
    }
    
    void TParseContext::checkIndexIsNotSpecified(const TSourceLoc &location, int index)
    {
        if (index != -1)
        {
            error(location,
                  "invalid layout qualifier: only valid when used with a fragment shader output in "
                  "ESSL version >= 3.00 and EXT_blend_func_extended is enabled",
                  "index");
        }
    }
    
    void TParseContext::checkBindingIsNotSpecified(const TSourceLoc &location, int binding)
    {
        if (binding != -1)
        {
            if (mShaderVersion < 310)
            {
                error(location,
                      "invalid layout qualifier: only valid when used with pixel local storage",
                      "binding");
            }
            else
            {
                error(location,
                      "invalid layout qualifier: only valid when used with opaque types or blocks",
                      "binding");
            }
        }
    }
    
    void TParseContext::checkOffsetIsNotSpecified(const TSourceLoc &location, int offset)
    {
        if (offset != -1)
        {
            error(location, "invalid layout qualifier: only valid when used with atomic counters",
                  "offset");
        }
    }
    
    void TParseContext::checkImageBindingIsValid(const TSourceLoc &location,
                                                 int binding,
                                                 int arrayTotalElementCount)
    {
        // Expects arraySize to be 1 when setting binding for only a single variable.
        if (binding >= 0 && binding + arrayTotalElementCount > mMaxImageUnits)
        {
            error(location, "image binding greater than gl_MaxImageUnits", "binding");
        }
    }
    
    void TParseContext::checkSamplerBindingIsValid(const TSourceLoc &location,
                                                   int binding,
                                                   int arrayTotalElementCount)
    {
        // Expects arraySize to be 1 when setting binding for only a single variable.
        if (binding >= 0 && binding + arrayTotalElementCount > mMaxCombinedTextureImageUnits)
        {
            error(location, "sampler binding greater than maximum texture units", "binding");
        }
    }
    
    void TParseContext::checkBlockBindingIsValid(const TSourceLoc &location,
                                                 const TQualifier &qualifier,
                                                 int binding,
                                                 int arraySize)
    {
        int size = (arraySize == 0 ? 1 : arraySize);
        if (qualifier == EvqUniform)
        {
            if (binding + size > mMaxUniformBufferBindings)
            {
                error(location, "uniform block binding greater than MAX_UNIFORM_BUFFER_BINDINGS",
                      "binding");
            }
        }
        else if (qualifier == EvqBuffer)
        {
            if (binding + size > mMaxShaderStorageBufferBindings)
            {
                error(location,
                      "shader storage block binding greater than MAX_SHADER_STORAGE_BUFFER_BINDINGS",
                      "binding");
            }
        }
    }
    void TParseContext::checkAtomicCounterBindingIsValid(const TSourceLoc &location, int binding)
    {
        if (binding >= mMaxAtomicCounterBindings)
        {
            error(location, "atomic counter binding greater than gl_MaxAtomicCounterBindings",
                  "binding");
        }
    }
    
    void TParseContext::checkPixelLocalStorageBindingIsValid(const TSourceLoc &location,
                                                             const TType &type)
    {
        TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
        if (type.isArray())
        {
            // PLS is not allowed in arrays.
            // TODO(anglebug.com/40096838): Consider allowing this once more backends are implemented.
            error(location, "pixel local storage handles cannot be aggregated in arrays", "array");
        }
        else if (layoutQualifier.binding < 0)
        {
            error(location, "pixel local storage requires a binding index", "layout qualifier");
        }
        // TODO(anglebug.com/40096838):
        else if (layoutQualifier.binding >= mMaxPixelLocalStoragePlanes)
        {
            error(location, "pixel local storage binding out of range", "layout qualifier");
        }
        else if (mPLSFormats.find(layoutQualifier.binding) != mPLSFormats.end())
        {
            error(location, "duplicate pixel local storage binding index",
                  std::to_string(layoutQualifier.binding).c_str());
        }
        else
        {
            mPLSFormats[layoutQualifier.binding] =
                ImageFormatToPLSFormat(layoutQualifier.imageInternalFormat);
            // "mPLSFormats" is how we know whether any pixel local storage uniforms have been declared,
            // so flush the queue of potential errors once mPLSFormats isn't empty.
            if (!mPLSPotentialErrors.empty())
            {
                for (const auto &[loc, op] : mPLSPotentialErrors)
                {
                    errorIfPLSDeclared(loc, op);
                }
                mPLSPotentialErrors.clear();
            }
        }
    }
    
    void TParseContext::checkUniformLocationInRange(const TSourceLoc &location,
                                                    int objectLocationCount,
                                                    const TLayoutQualifier &layoutQualifier)
    {
        int loc = layoutQualifier.location;
        if (loc >= 0)  // Shader-specified location
        {
            if (loc >= mMaxUniformLocations || objectLocationCount > mMaxUniformLocations ||
                static_cast<unsigned int>(loc) + static_cast<unsigned int>(objectLocationCount) >
                    static_cast<unsigned int>(mMaxUniformLocations))
            {
                error(location, "Uniform location out of range", "location");
            }
        }
    }
    
    void TParseContext::checkAttributeLocationInRange(const TSourceLoc &location,
                                                      int objectLocationCount,
                                                      const TLayoutQualifier &layoutQualifier)
    {
        int loc = layoutQualifier.location;
        if (loc >= 0)  // Shader-specified location
        {
            if (loc >= mMaxVertexAttribs || objectLocationCount > mMaxVertexAttribs ||
                static_cast<unsigned int>(loc) + static_cast<unsigned int>(objectLocationCount) >
                    static_cast<unsigned int>(mMaxVertexAttribs))
            {
                error(location, "Attribute location out of range", "location");
            }
        }
    }
    
    void TParseContext::checkDepthIsNotSpecified(const TSourceLoc &location, TLayoutDepth depth)
    {
        if (depth != EdUnspecified)
        {
            error(location, "invalid layout qualifier: only valid on gl_FragDepth",
                  getDepthString(depth));
        }
    }
    
    void TParseContext::checkYuvIsNotSpecified(const TSourceLoc &location, bool yuv)
    {
        if (yuv != false)
        {
            error(location, "invalid layout qualifier: only valid on program outputs", "yuv");
        }
    }
    
    void TParseContext::checkEarlyFragmentTestsIsNotSpecified(const TSourceLoc &location,
                                                              bool earlyFragmentTests)
    {
        if (earlyFragmentTests != false)
        {
            error(location,
                  "invalid layout qualifier: only valid when used with 'in' in a fragment shader",
                  "early_fragment_tests");
        }
    }
    
    void TParseContext::checkNoncoherentIsSpecified(const TSourceLoc &location, bool noncoherent)
    {
        if (noncoherent == false)
        {
            error(location,
                  "'noncoherent' qualifier must be used when "
                  "GL_EXT_shader_framebuffer_fetch_non_coherent extension is used",
                  "noncoherent");
        }
    }
    
    void TParseContext::checkNoncoherentIsNotSpecified(const TSourceLoc &location, bool noncoherent)
    {
        if (noncoherent != false)
        {
            error(location,
                  "invalid layout qualifier: only valid when used with 'gl_LastFragData' or the "
                  "variable decorated with 'inout' in a fragment shader",
                  "noncoherent");
        }
    }
    
    void TParseContext::checkTCSOutVarIndexIsValid(TIntermBinary *binaryExpression,
                                                   const TSourceLoc &location)
    {
        ASSERT(binaryExpression->getOp() == EOpIndexIndirect ||
               binaryExpression->getOp() == EOpIndexDirect);
        const TIntermSymbol *intermSymbol = binaryExpression->getRight()->getAsSymbolNode();
        if ((intermSymbol == nullptr) || (intermSymbol->getName() != "gl_InvocationID"))
        {
            error(location,
                  "tessellation-control per-vertex output l-value must be indexed with "
                  "gl_InvocationID",
                  "[");
        }
    }
    
    void TParseContext::functionCallRValueLValueErrorCheck(const TFunction *fnCandidate,
                                                           TIntermAggregate *fnCall)
    {
        for (size_t i = 0; i < fnCandidate->getParamCount(); ++i)
        {
            TQualifier qual        = fnCandidate->getParam(i)->getType().getQualifier();
            TIntermTyped *argument = (*(fnCall->getSequence()))[i]->getAsTyped();
            bool argumentIsRead    = (IsQualifierUnspecified(qual) || qual == EvqParamIn ||
                                   qual == EvqParamInOut || qual == EvqParamConst);
            if (argumentIsRead)
            {
                markStaticReadIfSymbol(argument);
                if (!IsImage(argument->getBasicType()))
                {
                    if (argument->getMemoryQualifier().writeonly)
                    {
                        error(argument->getLine(),
                              "Writeonly value cannot be passed for 'in' or 'inout' parameters.",
                              fnCall->functionName());
                        return;
                    }
                }
            }
            if (qual == EvqParamOut || qual == EvqParamInOut)
            {
                if (!checkCanBeLValue(argument->getLine(), "assign", argument))
                {
                    error(argument->getLine(),
                          "Constant value cannot be passed for 'out' or 'inout' parameters.",
                          fnCall->functionName());
                    return;
                }
            }
        }
    }
    
    void TParseContext::checkInvariantVariableQualifier(bool invariant,
                                                        const TQualifier qualifier,
                                                        const TSourceLoc &invariantLocation)
    {
        if (!invariant)
            return;
    
        if (mShaderVersion < 300)
        {
            // input variables in the fragment shader can be also qualified as invariant
            if (!sh::CanBeInvariantESSL1(qualifier))
            {
                error(invariantLocation, "Cannot be qualified as invariant.", "invariant");
            }
        }
        else
        {
            if (!sh::CanBeInvariantESSL3OrGreater(qualifier))
            {
                error(invariantLocation, "Cannot be qualified as invariant.", "invariant");
            }
        }
    }
    
    void TParseContext::checkAdvancedBlendEquationsNotSpecified(
        const TSourceLoc &location,
        const AdvancedBlendEquations &advancedBlendEquations,
        const TQualifier &qualifier)
    {
        if (advancedBlendEquations.any() && qualifier != EvqFragmentOut)
        {
            error(location,
                  "invalid layout qualifier: blending equation qualifiers are only permitted on the "
                  "fragment 'out' qualifier ",
                  "blend_support_qualifier");
        }
    }
    
    bool TParseContext::isExtensionEnabled(TExtension extension) const
    {
        return IsExtensionEnabled(extensionBehavior(), extension);
    }
    
    void TParseContext::handleExtensionDirective(const TSourceLoc &loc,
                                                 const char *extName,
                                                 const char *behavior)
    {
        angle::pp::SourceLocation srcLoc;
        srcLoc.file = loc.first_file;
        srcLoc.line = loc.first_line;
        mDirectiveHandler.handleExtension(srcLoc, extName, behavior);
    }
    
    void TParseContext::handlePragmaDirective(const TSourceLoc &loc,
                                              const char *name,
                                              const char *value,
                                              bool stdgl)
    {
        angle::pp::SourceLocation srcLoc;
        srcLoc.file = loc.first_file;
        srcLoc.line = loc.first_line;
        mDirectiveHandler.handlePragma(srcLoc, name, value, stdgl);
    }
    
    sh::WorkGroupSize TParseContext::getComputeShaderLocalSize() const
    {
        sh::WorkGroupSize result(-1);
        for (size_t i = 0u; i < result.size(); ++i)
        {
            if (mComputeShaderLocalSizeDeclared && mComputeShaderLocalSize[i] == -1)
            {
                result[i] = 1;
            }
            else
            {
                result[i] = mComputeShaderLocalSize[i];
            }
        }
        return result;
    }
    
    TIntermConstantUnion *TParseContext::addScalarLiteral(const TConstantUnion *constantUnion,
                                                          const TSourceLoc &line)
    {
        TIntermConstantUnion *node = new TIntermConstantUnion(
            constantUnion, TType(constantUnion->getType(), EbpUndefined, EvqConst));
        node->setLine(line);
        return node;
    }
    
    /////////////////////////////////////////////////////////////////////////////////
    //
    // Non-Errors.
    //
    /////////////////////////////////////////////////////////////////////////////////
    
    const TVariable *TParseContext::getNamedVariable(const TSourceLoc &location,
                                                     const ImmutableString &name,
                                                     const TSymbol *symbol)
    {
        if (!symbol)
        {
            error(location, "undeclared identifier", name);
            return nullptr;
        }
    
        if (!symbol->isVariable())
        {
            error(location, "variable expected", name);
            return nullptr;
        }
    
        const TVariable *variable = static_cast<const TVariable *>(symbol);
    
        if (!variable->extensions().empty() && variable->extensions()[0] != TExtension::UNDEFINED)
        {
            checkCanUseOneOfExtensions(location, variable->extensions());
        }
    
        // GLSL ES 3.1 Revision 4, 7.1.3 Compute Shader Special Variables
        if (getShaderType() == GL_COMPUTE_SHADER && !mComputeShaderLocalSizeDeclared &&
            variable->getType().getQualifier() == EvqWorkGroupSize)
        {
            error(location,
                  "It is an error to use gl_WorkGroupSize before declaring the local group size",
                  "gl_WorkGroupSize");
        }
    
        // If EXT_shader_framebuffer_fetch_non_coherent is used, gl_LastFragData should be decorated
        // with 'layout(noncoherent)' EXT_shader_framebuffer_fetch_non_coherent spec: "Unless the
        // GL_EXT_shader_framebuffer_fetch extension  has been enabled in addition, it's an error to use
        // gl_LastFragData if it hasn't been explicitly redeclared with layout(noncoherent)."
        if (isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch_non_coherent) &&
            !isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch) &&
            variable->getType().getQualifier() == EvqLastFragData)
        {
            checkNoncoherentIsSpecified(location, variable->getType().getLayoutQualifier().noncoherent);
        }
    
        // When EXT_separate_shader_objects is enabled, gl_Position and gl_PointSize must both be
        // redeclared before either is accessed:
        //
        // > The following vertex shader outputs may be redeclared at global scope to
        // > specify a built-in output interface, with or without special qualifiers:
        // >
        // >     gl_Position
        // >     gl_PointSize
        // >
        // >   When compiling shaders using either of the above variables, both such
        // >   variables must be redeclared prior to use.  ((Note:  This restriction
        // >   applies only to shaders using version 300 that enable the
        // >   EXT_separate_shader_objects extension; shaders not enabling the
        // >   extension do not have this requirement.))
        //
        // However, there are dEQP tests that enable all extensions and don't actually redeclare these
        // variables.  Per https://gitlab.khronos.org/opengl/API/-/issues/169, there are drivers that do
        // enforce this, but they fail linking instead of compilation.
        //
        // In ANGLE, we make sure that they are both redeclared before use if any is redeclared, but if
        // neither are redeclared, we don't fail compilation.  Currently, linking also doesn't fail in
        // ANGLE (similarly to almost all other drivers).
        if (isExtensionEnabled(TExtension::EXT_separate_shader_objects) &&
            mShaderType == GL_VERTEX_SHADER)
        {
            if (variable->getType().getQualifier() == EvqPosition ||
                variable->getType().getQualifier() == EvqPointSize)
            {
                mPositionOrPointSizeUsedForSeparateShaderObject = true;
                const bool eitherIsRedeclared = mPositionRedeclaredForSeparateShaderObject ||
                                                mPointSizeRedeclaredForSeparateShaderObject;
                const bool bothAreRedeclared = mPositionRedeclaredForSeparateShaderObject &&
                                               mPointSizeRedeclaredForSeparateShaderObject;
    
                if (eitherIsRedeclared && !bothAreRedeclared)
                {
                    error(location,
                          "When EXT_separate_shader_objects is enabled, both gl_Position and "
                          "gl_PointSize must be redeclared before either is used",
                          name);
                }
            }
        }
    
        return variable;
    }
    
    TIntermTyped *TParseContext::parseVariableIdentifier(const TSourceLoc &location,
                                                         const ImmutableString &name,
                                                         const TSymbol *symbol)
    {
        const TVariable *variable = getNamedVariable(location, name, symbol);
    
        if (!variable)
        {
            TIntermTyped *node = CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
            node->setLine(location);
            return node;
        }
    
        const TType &variableType = variable->getType();
        TIntermTyped *node        = nullptr;
    
        if (variable->getConstPointer() && variableType.canReplaceWithConstantUnion())
        {
            const TConstantUnion *constArray = variable->getConstPointer();
            node                             = new TIntermConstantUnion(constArray, variableType);
        }
        else if (variableType.getQualifier() == EvqWorkGroupSize && mComputeShaderLocalSizeDeclared)
        {
            // gl_WorkGroupSize can be used to size arrays according to the ESSL 3.10.4 spec, so it
            // needs to be added to the AST as a constant and not as a symbol.
            sh::WorkGroupSize workGroupSize = getComputeShaderLocalSize();
            TConstantUnion *constArray      = new TConstantUnion[3];
            for (size_t i = 0; i < 3; ++i)
            {
                constArray[i].setUConst(static_cast<unsigned int>(workGroupSize[i]));
            }
    
            ASSERT(variableType.getBasicType() == EbtUInt);
            ASSERT(variableType.getObjectSize() == 3);
    
            TType type(variableType);
            type.setQualifier(EvqConst);
            node = new TIntermConstantUnion(constArray, type);
        }
        else if ((mGeometryShaderInputPrimitiveType != EptUndefined) &&
                 (variableType.getQualifier() == EvqPerVertexIn))
        {
            ASSERT(symbolTable.getGlInVariableWithArraySize() != nullptr);
            node = new TIntermSymbol(symbolTable.getGlInVariableWithArraySize());
        }
        else
        {
            // gl_LastFragDepthARM and gl_LastFragStencilARM cannot be accessed if early_fragment_tests
            // is specified.
            if ((variableType.getQualifier() == EvqLastFragDepth ||
                 variableType.getQualifier() == EvqLastFragStencil) &&
                isEarlyFragmentTestsSpecified())
            {
                error(location,
                      "gl_LastFragDepthARM and gl_LastFragStencilARM cannot be accessed because "
                      "early_fragment_tests is specified",
                      name);
            }
    
            node = new TIntermSymbol(variable);
        }
        ASSERT(node != nullptr);
        node->setLine(location);
        return node;
    }
    
    void TParseContext::adjustRedeclaredBuiltInType(const TSourceLoc &line,
                                                    const ImmutableString &identifier,
                                                    TType *type)
    {
        if (identifier == "gl_ClipDistance")
        {
            const TQualifier qualifier = type->getQualifier();
            if ((mShaderType == GL_VERTEX_SHADER &&
                 !(qualifier == EvqVertexOut || qualifier == EvqVaryingOut)) ||
                (mShaderType == GL_FRAGMENT_SHADER && qualifier != EvqFragmentIn))
            {
                error(line, "invalid or missing storage qualifier", identifier);
                return;
            }
    
            type->setQualifier(EvqClipDistance);
        }
        else if (identifier == "gl_CullDistance")
        {
            const TQualifier qualifier = type->getQualifier();
            if ((mShaderType == GL_VERTEX_SHADER && qualifier != EvqVertexOut) ||
                (mShaderType == GL_FRAGMENT_SHADER && qualifier != EvqFragmentIn))
            {
                error(line, "invalid or missing storage qualifier", identifier);
                return;
            }
    
            type->setQualifier(EvqCullDistance);
        }
        else if (identifier == "gl_LastFragData")
        {
            type->setQualifier(EvqLastFragData);
        }
        else if (identifier == "gl_LastFragColorARM")
        {
            type->setQualifier(EvqLastFragColor);
        }
        else if (identifier == "gl_LastFragDepthARM")
        {
            type->setQualifier(EvqLastFragDepth);
        }
        else if (identifier == "gl_LastFragStencilARM")
        {
            type->setQualifier(EvqLastFragStencil);
        }
        else if (identifier == "gl_Position")
        {
            type->setQualifier(EvqPosition);
        }
        else if (identifier == "gl_PointSize")
        {
            type->setQualifier(EvqPointSize);
        }
    }
    
    // Initializers show up in several places in the grammar.  Have one set of
    // code to handle them here.
    //
    // Returns true on success.
    bool TParseContext::executeInitializer(const TSourceLoc &line,
                                           const ImmutableString &identifier,
                                           TType *type,
                                           TIntermTyped *initializer,
                                           TIntermBinary **initNode)
    {
        ASSERT(initNode != nullptr);
        ASSERT(*initNode == nullptr);
    
        if (type->isUnsizedArray())
        {
            // In case initializer is not an array or type has more dimensions than initializer, this
            // will default to setting array sizes to 1. We have not checked yet whether the initializer
            // actually is an array or not. Having a non-array initializer for an unsized array will
            // result in an error later, so we don't generate an error message here.
            type->sizeUnsizedArrays(initializer->getType().getArraySizes());
        }
    
        const TQualifier qualifier = type->getQualifier();
    
        bool constError = false;
        if (qualifier == EvqConst)
        {
            if (EvqConst != initializer->getType().getQualifier())
            {
                TInfoSinkBase reasonStream;
                reasonStream << "assigning non-constant to '" << *type << "'";
                error(line, reasonStream.c_str(), "=");
    
                // We're still going to declare the variable to avoid extra error messages.
                type->setQualifier(EvqTemporary);
                constError = true;
            }
        }
    
        TVariable *variable = nullptr;
        if (!declareVariable(line, identifier, type, &variable))
        {
            return false;
        }
    
        if (constError)
        {
            return false;
        }
    
        bool nonConstGlobalInitializers =
            IsExtensionEnabled(mDirectiveHandler.extensionBehavior(),
                               TExtension::EXT_shader_non_constant_global_initializers);
        bool globalInitWarning = false;
        if (symbolTable.atGlobalLevel() &&
            !ValidateGlobalInitializer(initializer, mShaderVersion, sh::IsWebGLBasedSpec(mShaderSpec),
                                       nonConstGlobalInitializers, &globalInitWarning))
        {
            // Error message does not completely match behavior with ESSL 1.00, but
            // we want to steer developers towards only using constant expressions.
            error(line, "global variable initializers must be constant expressions", "=");
            return false;
        }
        if (globalInitWarning)
        {
            warning(
                line,
                "global variable initializers should be constant expressions "
                "(uniforms and globals are allowed in global initializers for legacy compatibility)",
                "=");
        }
    
        // identifier must be of type constant, a global, or a temporary
        if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal) && (qualifier != EvqConst))
        {
            error(line, " cannot initialize this type of qualifier ",
                  variable->getType().getQualifierString());
            return false;
        }
    
        TIntermSymbol *intermSymbol = new TIntermSymbol(variable);
        intermSymbol->setLine(line);
    
        if (!binaryOpCommonCheck(EOpInitialize, intermSymbol, initializer, line))
        {
            assignError(line, "=", variable->getType(), initializer->getType());
            return false;
        }
    
        if (qualifier == EvqConst)
        {
            // Save the constant folded value to the variable if possible.
            const TConstantUnion *constArray = initializer->getConstantValue();
            if (constArray)
            {
                variable->shareConstPointer(constArray);
                if (initializer->getType().canReplaceWithConstantUnion())
                {
                    ASSERT(*initNode == nullptr);
                    return true;
                }
            }
        }
    
        *initNode = new TIntermBinary(EOpInitialize, intermSymbol, initializer);
        markStaticReadIfSymbol(initializer);
        (*initNode)->setLine(line);
        return true;
    }
    
    TIntermNode *TParseContext::addConditionInitializer(const TPublicType &pType,
                                                        const ImmutableString &identifier,
                                                        TIntermTyped *initializer,
                                                        const TSourceLoc &loc)
    {
        checkIsScalarBool(loc, pType);
        TIntermBinary *initNode = nullptr;
        TType *type             = new TType(pType);
        if (executeInitializer(loc, identifier, type, initializer, &initNode))
        {
            // The initializer is valid. The init condition needs to have a node - either the
            // initializer node, or a constant node in case the initialized variable is const and won't
            // be recorded in the AST.
            if (initNode == nullptr)
            {
                return initializer;
            }
            else
            {
                TIntermDeclaration *declaration = new TIntermDeclaration();
                declaration->appendDeclarator(initNode);
                return declaration;
            }
        }
        return nullptr;
    }
    
    TIntermNode *TParseContext::addLoop(TLoopType type,
                                        TIntermNode *init,
                                        TIntermNode *cond,
                                        TIntermTyped *expr,
                                        TIntermNode *body,
                                        const TSourceLoc &line)
    {
        TIntermNode *node       = nullptr;
        TIntermTyped *typedCond = nullptr;
        if (cond)
        {
            markStaticReadIfSymbol(cond);
            typedCond = cond->getAsTyped();
        }
        if (expr)
        {
            markStaticReadIfSymbol(expr);
        }
        // In case the loop body was not parsed as a block and contains a statement that simply refers
        // to a variable, we need to mark it as statically used.
        if (body)
        {
            markStaticReadIfSymbol(body);
        }
        if (cond == nullptr || typedCond)
        {
            if (type == ELoopDoWhile && typedCond)
            {
                checkIsScalarBool(line, typedCond);
            }
            // In the case of other loops, it was checked before that the condition is a scalar boolean.
            ASSERT(mDiagnostics->numErrors() > 0 || typedCond == nullptr ||
                   (typedCond->getBasicType() == EbtBool && !typedCond->isArray() &&
                    !typedCond->isVector()));
    
            node = new TIntermLoop(type, init, typedCond, expr, EnsureLoopBodyBlock(body));
            node->setLine(line);
            return node;
        }
    
        ASSERT(type != ELoopDoWhile);
    
        TIntermDeclaration *declaration = cond->getAsDeclarationNode();
        ASSERT(declaration);
        TIntermBinary *declarator = declaration->getSequence()->front()->getAsBinaryNode();
        ASSERT(declarator->getLeft()->getAsSymbolNode());
    
        // The condition is a declaration. In the AST representation we don't support declarations as
        // loop conditions. Wrap the loop to a block that declares the condition variable and contains
        // the loop.
        TIntermBlock *block = new TIntermBlock();
    
        TIntermDeclaration *declareCondition = new TIntermDeclaration();
        declareCondition->appendDeclarator(declarator->getLeft()->deepCopy());
        block->appendStatement(declareCondition);
    
        TIntermBinary *conditionInit = new TIntermBinary(EOpAssign, declarator->getLeft()->deepCopy(),
                                                         declarator->getRight()->deepCopy());
        TIntermLoop *loop = new TIntermLoop(type, init, conditionInit, expr, EnsureLoopBodyBlock(body));
        block->appendStatement(loop);
        loop->setLine(line);
        block->setLine(line);
        return block;
    }
    
    TIntermNode *TParseContext::addIfElse(TIntermTyped *cond,
                                          TIntermNodePair code,
                                          const TSourceLoc &loc)
    {
        bool isScalarBool = checkIsScalarBool(loc, cond);
        // In case the conditional statements were not parsed as blocks and contain a statement that
        // simply refers to a variable, we need to mark them as statically used.
        if (code.node1)
        {
            markStaticReadIfSymbol(code.node1);
        }
        if (code.node2)
        {
            markStaticReadIfSymbol(code.node2);
        }
    
        // For compile time constant conditions, prune the code now.
        if (isScalarBool && cond->getAsConstantUnion())
        {
            if (cond->getAsConstantUnion()->getBConst(0) == true)
            {
                return EnsureBlock(code.node1);
            }
            else
            {
                return EnsureBlock(code.node2);
            }
        }
    
        TIntermIfElse *node = new TIntermIfElse(cond, EnsureBlock(code.node1), EnsureBlock(code.node2));
        markStaticReadIfSymbol(cond);
        node->setLine(loc);
    
        return node;
    }
    
    void TParseContext::addFullySpecifiedType(TPublicType *typeSpecifier)
    {
        checkPrecisionSpecified(typeSpecifier->getLine(), typeSpecifier->precision,
                                typeSpecifier->getBasicType());
    
        if (mShaderVersion < 300 && typeSpecifier->isArray())
        {
            error(typeSpecifier->getLine(), "not supported", "first-class array");
            typeSpecifier->clearArrayness();
        }
    }
    
    TPublicType TParseContext::addFullySpecifiedType(const TTypeQualifierBuilder &typeQualifierBuilder,
                                                     const TPublicType &typeSpecifier)
    {
        TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
    
        TPublicType returnType     = typeSpecifier;
        returnType.qualifier       = typeQualifier.qualifier;
        returnType.invariant       = typeQualifier.invariant;
        returnType.precise         = typeQualifier.precise;
        returnType.layoutQualifier = typeQualifier.layoutQualifier;
        returnType.memoryQualifier = typeQualifier.memoryQualifier;
        returnType.precision       = typeSpecifier.precision;
    
        if (typeQualifier.precision != EbpUndefined)
        {
            returnType.precision = typeQualifier.precision;
        }
    
        checkPrecisionSpecified(typeSpecifier.getLine(), returnType.precision,
                                typeSpecifier.getBasicType());
    
        checkInvariantVariableQualifier(returnType.invariant, returnType.qualifier,
                                        typeSpecifier.getLine());
    
        checkWorkGroupSizeIsNotSpecified(typeSpecifier.getLine(), returnType.layoutQualifier);
    
        checkEarlyFragmentTestsIsNotSpecified(typeSpecifier.getLine(),
                                              returnType.layoutQualifier.earlyFragmentTests);
    
        if (returnType.qualifier == EvqSampleIn || returnType.qualifier == EvqSampleOut ||
            returnType.qualifier == EvqNoPerspectiveSampleIn ||
            returnType.qualifier == EvqNoPerspectiveSampleOut)
        {
            mSampleQualifierSpecified = true;
        }
    
        if (mShaderVersion < 300)
        {
            if (typeSpecifier.isArray())
            {
                error(typeSpecifier.getLine(), "not supported", "first-class array");
                returnType.clearArrayness();
            }
    
            if (returnType.qualifier == EvqAttribute &&
                (typeSpecifier.getBasicType() == EbtBool || typeSpecifier.getBasicType() == EbtInt))
            {
                error(typeSpecifier.getLine(), "cannot be bool or int",
                      getQualifierString(returnType.qualifier));
            }
    
            if ((returnType.qualifier == EvqVaryingIn || returnType.qualifier == EvqVaryingOut) &&
                (typeSpecifier.getBasicType() == EbtBool || typeSpecifier.getBasicType() == EbtInt))
            {
                error(typeSpecifier.getLine(), "cannot be bool or int",
                      getQualifierString(returnType.qualifier));
            }
        }
        else
        {
            if (!returnType.layoutQualifier.isEmpty())
            {
                checkIsAtGlobalLevel(typeSpecifier.getLine(), "layout");
            }
            if (sh::IsVarying(returnType.qualifier) || returnType.qualifier == EvqVertexIn ||
                returnType.qualifier == EvqFragmentOut || returnType.qualifier == EvqFragmentInOut)
            {
                checkInputOutputTypeIsValidES3(returnType.qualifier, typeSpecifier,
                                               typeSpecifier.getLine());
            }
            if (returnType.qualifier == EvqComputeIn)
            {
                error(typeSpecifier.getLine(), "'in' can be only used to specify the local group size",
                      "in");
            }
        }
    
        return returnType;
    }
    
    void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier,
                                                       const TPublicType &type,
                                                       const TSourceLoc &qualifierLocation)
    {
        // An input/output variable can never be bool or a sampler. Samplers are checked elsewhere.
        if (type.getBasicType() == EbtBool)
        {
            error(qualifierLocation, "cannot be bool", getQualifierString(qualifier));
        }
    
        // Specific restrictions apply for vertex shader inputs and fragment shader outputs.
        switch (qualifier)
        {
            case EvqVertexIn:
                // ESSL 3.00 section 4.3.4
                if (type.isArray())
                {
                    error(qualifierLocation, "cannot be array", getQualifierString(qualifier));
                }
                // Vertex inputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck
                return;
            case EvqFragmentOut:
            case EvqFragmentInOut:
                // ESSL 3.00 section 4.3.6
                if (type.typeSpecifierNonArray.isMatrix())
                {
                    error(qualifierLocation, "cannot be matrix", getQualifierString(qualifier));
                }
                // Fragment outputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck
                return;
            default:
                break;
        }
    
        // Vertex shader outputs / fragment shader inputs have a different, slightly more lenient set of
        // restrictions.
        bool typeContainsIntegers =
            (type.getBasicType() == EbtInt || type.getBasicType() == EbtUInt ||
             type.isStructureContainingType(EbtInt) || type.isStructureContainingType(EbtUInt));
        bool extendedShaderTypes = mShaderVersion >= 320 ||
                                   isExtensionEnabled(TExtension::EXT_geometry_shader) ||
                                   isExtensionEnabled(TExtension::OES_geometry_shader) ||
                                   isExtensionEnabled(TExtension::EXT_tessellation_shader) ||
                                   isExtensionEnabled(TExtension::OES_tessellation_shader);
        if (typeContainsIntegers && qualifier != EvqFlatIn && qualifier != EvqFlatOut &&
            (!extendedShaderTypes || mShaderType == GL_FRAGMENT_SHADER))
        {
            error(qualifierLocation, "must use 'flat' interpolation here",
                  getQualifierString(qualifier));
        }
    
        if (type.getBasicType() == EbtStruct)
        {
            // ESSL 3.00 sections 4.3.4 and 4.3.6.
            // These restrictions are only implied by the ESSL 3.00 spec, but
            // the ESSL 3.10 spec lists these restrictions explicitly.
            if (type.isArray())
            {
                error(qualifierLocation, "cannot be an array of structures",
                      getQualifierString(qualifier));
            }
            if (type.isStructureContainingArrays())
            {
                error(qualifierLocation, "cannot be a structure containing an array",
                      getQualifierString(qualifier));
            }
            if (type.isStructureContainingType(EbtStruct))
            {
                error(qualifierLocation, "cannot be a structure containing a structure",
                      getQualifierString(qualifier));
            }
            if (type.isStructureContainingType(EbtBool))
            {
                error(qualifierLocation, "cannot be a structure containing a bool",
                      getQualifierString(qualifier));
            }
        }
    }
    
    void TParseContext::checkLocalVariableConstStorageQualifier(const TQualifierWrapperBase &qualifier)
    {
        if (qualifier.getType() == QtStorage)
        {
            const TStorageQualifierWrapper &storageQualifier =
                static_cast<const TStorageQualifierWrapper &>(qualifier);
            if (!declaringFunction() && storageQualifier.getQualifier() != EvqConst &&
                !symbolTable.atGlobalLevel())
            {
                error(storageQualifier.getLine(),
                      "Local variables can only use the const storage qualifier.",
                      storageQualifier.getQualifierString());
            }
        }
    }
    
    void TParseContext::checkMemoryQualifierIsNotSpecified(const TMemoryQualifier &memoryQualifier,
                                                           const TSourceLoc &location)
    {
        const std::string reason(
            "Only allowed with shader storage blocks, variables declared within shader storage blocks "
            "and variables declared as image types.");
        if (memoryQualifier.readonly)
        {
            error(location, reason.c_str(), "readonly");
        }
        if (memoryQualifier.writeonly)
        {
            error(location, reason.c_str(), "writeonly");
        }
        if (memoryQualifier.coherent)
        {
            error(location, reason.c_str(), "coherent");
        }
        if (memoryQualifier.restrictQualifier)
        {
            error(location, reason.c_str(), "restrict");
        }
        if (memoryQualifier.volatileQualifier)
        {
            error(location, reason.c_str(), "volatile");
        }
    }
    
    // Make sure there is no offset overlapping, and store the newly assigned offset to "type" in
    // intermediate tree.
    void TParseContext::checkAtomicCounterOffsetDoesNotOverlap(bool forceAppend,
                                                               const TSourceLoc &loc,
                                                               TType *type)
    {
        const size_t size = type->isArray() ? kAtomicCounterArrayStride * type->getArraySizeProduct()
                                            : kAtomicCounterSize;
        TLayoutQualifier layoutQualifier = type->getLayoutQualifier();
        auto &bindingState               = mAtomicCounterBindingStates[layoutQualifier.binding];
        int offset;
        if (layoutQualifier.offset == -1 || forceAppend)
        {
            offset = bindingState.appendSpan(size);
        }
        else
        {
            offset = bindingState.insertSpan(layoutQualifier.offset, size);
        }
        if (offset == -1)
        {
            error(loc, "Offset overlapping", "atomic counter");
            return;
        }
        layoutQualifier.offset = offset;
        type->setLayoutQualifier(layoutQualifier);
    }
    
    void TParseContext::checkAtomicCounterOffsetAlignment(const TSourceLoc &location, const TType &type)
    {
        TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
    
        // OpenGL ES 3.1 Table 6.5, Atomic counter offset must be a multiple of 4
        if (layoutQualifier.offset % 4 != 0)
        {
            error(location, "Offset must be multiple of 4", "atomic counter");
        }
    }
    
    void TParseContext::checkAtomicCounterOffsetLimit(const TSourceLoc &location, const TType &type)
    {
        TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
    
        if (layoutQualifier.offset >= mMaxAtomicCounterBufferSize)
        {
            error(location, "Offset must not exceed the maximum atomic counter buffer size",
                  "atomic counter");
        }
    }
    
    void TParseContext::checkAtomicCounterOffsetIsValid(bool forceAppend,
                                                        const TSourceLoc &loc,
                                                        TType *type)
    {
        checkAtomicCounterOffsetDoesNotOverlap(forceAppend, loc, type);
        checkAtomicCounterOffsetAlignment(loc, *type);
        checkAtomicCounterOffsetLimit(loc, *type);
    }
    
    void TParseContext::checkGeometryShaderInputAndSetArraySize(const TSourceLoc &location,
                                                                const ImmutableString &token,
                                                                TType *type)
    {
        if (IsGeometryShaderInput(mShaderType, type->getQualifier()))
        {
            if (type->isArray() && type->getOutermostArraySize() == 0u)
            {
                // Set size for the unsized geometry shader inputs if they are declared after a valid
                // input primitive declaration.
                if (mGeometryShaderInputPrimitiveType != EptUndefined)
                {
                    ASSERT(symbolTable.getGlInVariableWithArraySize() != nullptr);
                    type->sizeOutermostUnsizedArray(
                        symbolTable.getGlInVariableWithArraySize()->getType().getOutermostArraySize());
                }
                else
                {
                    // [GLSL ES 3.2 SPEC Chapter 4.4.1.2]
                    // An input can be declared without an array size if there is a previous layout
                    // which specifies the size.
                    warning(location,
                            "Missing a valid input primitive declaration before declaring an unsized "
                            "array input",
                            "Deferred");
                    mDeferredArrayTypesToSize.push_back(type);
                }
            }
            else if (type->isArray())
            {
                setGeometryShaderInputArraySize(type->getOutermostArraySize(), location);
            }
            else
            {
                error(location, "Geometry shader input variable must be declared as an array", token);
            }
        }
    }
    
    void TParseContext::checkTessellationShaderUnsizedArraysAndSetSize(const TSourceLoc &location,
                                                                       const ImmutableString &token,
                                                                       TType *type)
    {
        TQualifier qualifier = type->getQualifier();
        if (!IsTessellationControlShaderOutput(mShaderType, qualifier) &&
            !IsTessellationControlShaderInput(mShaderType, qualifier) &&
            !IsTessellationEvaluationShaderInput(mShaderType, qualifier))
        {
            return;
        }
    
        // Such variables must be declared as arrays or inside output blocks declared as arrays.
        if (!type->isArray())
        {
            error(location, "Tessellation interface variables must be declared as an array", token);
            return;
        }
    
        // If a size is specified, it must match the maximum patch size.
        unsigned int outermostSize = type->getOutermostArraySize();
        if (outermostSize == 0u)
        {
            switch (qualifier)
            {
                case EvqTessControlIn:
                case EvqTessEvaluationIn:
                case EvqSmoothIn:
                case EvqFlatIn:
                case EvqNoPerspectiveIn:
                case EvqCentroidIn:
                case EvqSampleIn:
                case EvqNoPerspectiveCentroidIn:
                case EvqNoPerspectiveSampleIn:
                    // Declaring an array size is optional. If no size is specified, it will be taken
                    // from the implementation-dependent maximum patch size (gl_MaxPatchVertices).
                    ASSERT(mMaxPatchVertices > 0);
                    type->sizeOutermostUnsizedArray(mMaxPatchVertices);
                    break;
                case EvqTessControlOut:
                case EvqTessEvaluationOut:
                case EvqSmoothOut:
                case EvqFlatOut:
                case EvqNoPerspectiveOut:
                case EvqCentroidOut:
                case EvqSampleOut:
                case EvqNoPerspectiveCentroidOut:
                case EvqNoPerspectiveSampleOut:
                    // Declaring an array size is optional. If no size is specified, it will be taken
                    // from output patch size declared in the shader.  If the patch size is not yet
                    // declared, this is deferred until such time as it does.
                    if (mTessControlShaderOutputVertices == 0)
                    {
                        mDeferredArrayTypesToSize.push_back(type);
                    }
                    else
                    {
                        type->sizeOutermostUnsizedArray(mTessControlShaderOutputVertices);
                    }
                    break;
                default:
                    UNREACHABLE();
                    break;
            }
            return;
        }
    
        if (IsTessellationControlShaderInput(mShaderType, qualifier) ||
            IsTessellationEvaluationShaderInput(mShaderType, qualifier))
        {
            if (outermostSize != static_cast<unsigned int>(mMaxPatchVertices))
            {
                error(location,
                      "If a size is specified for a tessellation control or evaluation user-defined "
                      "input variable, it must match the maximum patch size (gl_MaxPatchVertices).",
                      token);
            }
        }
        else if (IsTessellationControlShaderOutput(mShaderType, qualifier))
        {
            if (outermostSize != static_cast<unsigned int>(mTessControlShaderOutputVertices) &&
                mTessControlShaderOutputVertices != 0)
            {
                error(location,
                      "If a size is specified for a tessellation control user-defined per-vertex "
                      "output variable, it must match the the number of vertices in the output "
                      "patch.",
                      token);
            }
        }
    }
    
    TIntermDeclaration *TParseContext::parseSingleDeclaration(
        TPublicType &publicType,
        const TSourceLoc &identifierOrTypeLocation,
        const ImmutableString &identifier)
    {
        TType *type = new TType(publicType);
        if (mCompileOptions.flattenPragmaSTDGLInvariantAll &&
            mDirectiveHandler.pragma().stdgl.invariantAll)
        {
            TQualifier qualifier = type->getQualifier();
    
            // The directive handler has already taken care of rejecting invalid uses of this pragma
            // (for example, in ESSL 3.00 fragment shaders), so at this point, flatten it into all
            // affected variable declarations:
            //
            // 1. Built-in special variables which are inputs to the fragment shader. (These are handled
            // elsewhere, in TranslatorGLSL.)
            //
            // 2. Outputs from vertex shaders in ESSL 1.00 and 3.00 (EvqVaryingOut and EvqVertexOut). It
            // is actually less likely that there will be bugs in the handling of ESSL 3.00 shaders, but
            // the way this is currently implemented we have to enable this compiler option before
            // parsing the shader and determining the shading language version it uses. If this were
            // implemented as a post-pass, the workaround could be more targeted.
            if (qualifier == EvqVaryingOut || qualifier == EvqVertexOut)
            {
                type->setInvariant(true);
            }
        }
    
        if (identifier == "gl_FragDepth")
        {
            if (type->getQualifier() == EvqFragmentOut)
            {
                type->setQualifier(EvqFragDepth);
            }
            else
            {
                error(identifierOrTypeLocation,
                      "gl_FragDepth can only be redeclared as fragment output", identifier);
            }
        }
    
        checkGeometryShaderInputAndSetArraySize(identifierOrTypeLocation, identifier, type);
        checkTessellationShaderUnsizedArraysAndSetSize(identifierOrTypeLocation, identifier, type);
    
        declarationQualifierErrorCheck(type->getQualifier(), publicType.layoutQualifier,
                                       identifierOrTypeLocation);
    
        bool emptyDeclaration                  = (identifier == "");
        mDeferredNonEmptyDeclarationErrorCheck = emptyDeclaration;
    
        TIntermSymbol *symbol = nullptr;
        if (emptyDeclaration)
        {
            emptyDeclarationErrorCheck(*type, identifierOrTypeLocation);
            // In most cases we don't need to create a symbol node for an empty declaration.
            // But if the empty declaration is declaring a struct type, the symbol node will store that.
            if (type->getBasicType() == EbtStruct)
            {
                TVariable *emptyVariable =
                    new TVariable(&symbolTable, kEmptyImmutableString, type, SymbolType::Empty);
                symbol = new TIntermSymbol(emptyVariable);
            }
            else if (IsAtomicCounter(publicType.getBasicType()))
            {
                setAtomicCounterBindingDefaultOffset(publicType, identifierOrTypeLocation);
            }
        }
        else
        {
            nonEmptyDeclarationErrorCheck(publicType, identifierOrTypeLocation);
    
            checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, type);
            checkDeclarationIsValidArraySize(identifierOrTypeLocation, identifier, type);
    
            if (IsAtomicCounter(type->getBasicType()))
            {
                checkAtomicCounterOffsetIsValid(false, identifierOrTypeLocation, type);
            }
    
            TVariable *variable = nullptr;
            if (declareVariable(identifierOrTypeLocation, identifier, type, &variable))
            {
                symbol = new TIntermSymbol(variable);
            }
        }
    
        adjustRedeclaredBuiltInType(identifierOrTypeLocation, identifier, type);
    
        TIntermDeclaration *declaration = new TIntermDeclaration();
        declaration->setLine(identifierOrTypeLocation);
        if (symbol)
        {
            symbol->setLine(identifierOrTypeLocation);
            declaration->appendDeclarator(symbol);
        }
        return declaration;
    }
    
    TIntermDeclaration *TParseContext::parseSingleArrayDeclaration(
        TPublicType &elementType,
        const TSourceLoc &identifierLocation,
        const ImmutableString &identifier,
        const TSourceLoc &indexLocation,
        const TVector<unsigned int> &arraySizes)
    {
        mDeferredNonEmptyDeclarationErrorCheck = false;
    
        declarationQualifierErrorCheck(elementType.qualifier, elementType.layoutQualifier,
                                       identifierLocation);
    
        nonEmptyDeclarationErrorCheck(elementType, identifierLocation);
    
        checkIsValidTypeAndQualifierForArray(indexLocation, elementType);
    
        TType *arrayType = new TType(elementType);
        arrayType->makeArrays(arraySizes);
    
        checkArrayOfArraysInOut(indexLocation, elementType, *arrayType);
    
        checkGeometryShaderInputAndSetArraySize(indexLocation, identifier, arrayType);
        checkTessellationShaderUnsizedArraysAndSetSize(indexLocation, identifier, arrayType);
    
        checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, arrayType);
        checkDeclarationIsValidArraySize(identifierLocation, identifier, arrayType);
    
        if (IsAtomicCounter(arrayType->getBasicType()))
        {
            checkAtomicCounterOffsetIsValid(false, identifierLocation, arrayType);
        }
    
        adjustRedeclaredBuiltInType(identifierLocation, identifier, arrayType);
    
        TIntermDeclaration *declaration = new TIntermDeclaration();
        declaration->setLine(identifierLocation);
    
        TVariable *variable = nullptr;
        if (declareVariable(identifierLocation, identifier, arrayType, &variable))
        {
            TIntermSymbol *symbol = new TIntermSymbol(variable);
            symbol->setLine(identifierLocation);
            declaration->appendDeclarator(symbol);
        }
    
        return declaration;
    }
    
    TIntermDeclaration *TParseContext::parseSingleInitDeclaration(const TPublicType &publicType,
                                                                  const TSourceLoc &identifierLocation,
                                                                  const ImmutableString &identifier,
                                                                  const TSourceLoc &initLocation,
                                                                  TIntermTyped *initializer)
    {
        mDeferredNonEmptyDeclarationErrorCheck = false;
    
        declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier,
                                       identifierLocation);
    
        nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
    
        TIntermDeclaration *declaration = new TIntermDeclaration();
        declaration->setLine(identifierLocation);
    
        TIntermBinary *initNode = nullptr;
        TType *type             = new TType(publicType);
        if (executeInitializer(identifierLocation, identifier, type, initializer, &initNode))
        {
            if (initNode)
            {
                declaration->appendDeclarator(initNode);
            }
            else if (publicType.isStructSpecifier())
            {
                // The initialization got constant folded.  If it's a struct, declare the struct anyway.
                TVariable *emptyVariable =
                    new TVariable(&symbolTable, kEmptyImmutableString, type, SymbolType::Empty);
                TIntermSymbol *symbol = new TIntermSymbol(emptyVariable);
                symbol->setLine(publicType.getLine());
                declaration->appendDeclarator(symbol);
            }
        }
        return declaration;
    }
    
    TIntermDeclaration *TParseContext::parseSingleArrayInitDeclaration(
        TPublicType &elementType,
        const TSourceLoc &identifierLocation,
        const ImmutableString &identifier,
        const TSourceLoc &indexLocation,
        const TVector<unsigned int> &arraySizes,
        const TSourceLoc &initLocation,
        TIntermTyped *initializer)
    {
        mDeferredNonEmptyDeclarationErrorCheck = false;
    
        declarationQualifierErrorCheck(elementType.qualifier, elementType.layoutQualifier,
                                       identifierLocation);
    
        nonEmptyDeclarationErrorCheck(elementType, identifierLocation);
    
        checkIsValidTypeAndQualifierForArray(indexLocation, elementType);
    
        TType *arrayType = new TType(elementType);
        arrayType->makeArrays(arraySizes);
    
        TIntermDeclaration *declaration = new TIntermDeclaration();
        declaration->setLine(identifierLocation);
    
        // initNode will correspond to the whole of "type b[n] = initializer".
        TIntermBinary *initNode = nullptr;
        if (executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode))
        {
            if (initNode)
            {
                declaration->appendDeclarator(initNode);
            }
        }
    
        return declaration;
    }
    
    TIntermGlobalQualifierDeclaration *TParseContext::parseGlobalQualifierDeclaration(
        const TTypeQualifierBuilder &typeQualifierBuilder,
        const TSourceLoc &identifierLoc,
        const ImmutableString &identifier,
        const TSymbol *symbol)
    {
        TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
    
        if (!typeQualifier.invariant && !typeQualifier.precise)
        {
            error(identifierLoc, "Expected invariant or precise", identifier);
            return nullptr;
        }
        if (typeQualifier.invariant && !checkIsAtGlobalLevel(identifierLoc, "invariant varying"))
        {
            return nullptr;
        }
        if (!symbol)
        {
            error(identifierLoc, "undeclared identifier declared as invariant or precise", identifier);
            return nullptr;
        }
        if (!IsQualifierUnspecified(typeQualifier.qualifier))
        {
            error(identifierLoc, "invariant or precise declaration specifies qualifier",
                  getQualifierString(typeQualifier.qualifier));
        }
        if (typeQualifier.precision != EbpUndefined)
        {
            error(identifierLoc, "invariant or precise declaration specifies precision",
                  getPrecisionString(typeQualifier.precision));
        }
        if (!typeQualifier.layoutQualifier.isEmpty())
        {
            error(identifierLoc, "invariant or precise declaration specifies layout", "'layout'");
        }
    
        const TVariable *variable = getNamedVariable(identifierLoc, identifier, symbol);
        if (!variable)
        {
            return nullptr;
        }
        const TType &type = variable->getType();
    
        checkInvariantVariableQualifier(typeQualifier.invariant, type.getQualifier(),
                                        typeQualifier.line);
        checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line);
    
        if (typeQualifier.invariant)
        {
            symbolTable.addInvariantVarying(*variable);
        }
    
        TIntermSymbol *intermSymbol = new TIntermSymbol(variable);
        intermSymbol->setLine(identifierLoc);
    
        return new TIntermGlobalQualifierDeclaration(intermSymbol, typeQualifier.precise,
                                                     identifierLoc);
    }
    
    void TParseContext::parseDeclarator(TPublicType &publicType,
                                        const TSourceLoc &identifierLocation,
                                        const ImmutableString &identifier,
                                        TIntermDeclaration *declarationOut)
    {
        // If the declaration starting this declarator list was empty (example: int,), some checks were
        // not performed.
        if (mDeferredNonEmptyDeclarationErrorCheck)
        {
            nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
            mDeferredNonEmptyDeclarationErrorCheck = false;
        }
    
        checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
    
        TType *type = new TType(publicType);
    
        checkGeometryShaderInputAndSetArraySize(identifierLocation, identifier, type);
        checkTessellationShaderUnsizedArraysAndSetSize(identifierLocation, identifier, type);
    
        checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, type);
        checkDeclarationIsValidArraySize(identifierLocation, identifier, type);
    
        if (IsAtomicCounter(type->getBasicType()))
        {
            checkAtomicCounterOffsetIsValid(true, identifierLocation, type);
        }
    
        adjustRedeclaredBuiltInType(identifierLocation, identifier, type);
    
        TVariable *variable = nullptr;
        if (declareVariable(identifierLocation, identifier, type, &variable))
        {
            TIntermSymbol *symbol = new TIntermSymbol(variable);
            symbol->setLine(identifierLocation);
            declarationOut->appendDeclarator(symbol);
        }
    }
    
    void TParseContext::parseArrayDeclarator(TPublicType &elementType,
                                             const TSourceLoc &identifierLocation,
                                             const ImmutableString &identifier,
                                             const TSourceLoc &arrayLocation,
                                             const TVector<unsigned int> &arraySizes,
                                             TIntermDeclaration *declarationOut)
    {
        // If the declaration starting this declarator list was empty (example: int,), some checks were
        // not performed.
        if (mDeferredNonEmptyDeclarationErrorCheck)
        {
            nonEmptyDeclarationErrorCheck(elementType, identifierLocation);
            mDeferredNonEmptyDeclarationErrorCheck = false;
        }
    
        checkDeclaratorLocationIsNotSpecified(identifierLocation, elementType);
    
        if (checkIsValidTypeAndQualifierForArray(arrayLocation, elementType))
        {
            TType *arrayType = new TType(elementType);
            arrayType->makeArrays(arraySizes);
    
            checkGeometryShaderInputAndSetArraySize(identifierLocation, identifier, arrayType);
            checkTessellationShaderUnsizedArraysAndSetSize(identifierLocation, identifier, arrayType);
    
            checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, arrayType);
            checkDeclarationIsValidArraySize(identifierLocation, identifier, arrayType);
    
            if (IsAtomicCounter(arrayType->getBasicType()))
            {
                checkAtomicCounterOffsetDoesNotOverlap(true, identifierLocation, arrayType);
    
                checkAtomicCounterOffsetAlignment(identifierLocation, *arrayType);
            }
    
            adjustRedeclaredBuiltInType(identifierLocation, identifier, arrayType);
    
            TVariable *variable = nullptr;
            if (declareVariable(identifierLocation, identifier, arrayType, &variable))
            {
                TIntermSymbol *symbol = new TIntermSymbol(variable);
                symbol->setLine(identifierLocation);
                declarationOut->appendDeclarator(symbol);
            }
        }
    }
    
    void TParseContext::parseInitDeclarator(const TPublicType &publicType,
                                            const TSourceLoc &identifierLocation,
                                            const ImmutableString &identifier,
                                            const TSourceLoc &initLocation,
                                            TIntermTyped *initializer,
                                            TIntermDeclaration *declarationOut)
    {
        // If the declaration starting this declarator list was empty (example: int,), some checks were
        // not performed.
        if (mDeferredNonEmptyDeclarationErrorCheck)
        {
            nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
            mDeferredNonEmptyDeclarationErrorCheck = false;
        }
    
        checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
    
        TIntermBinary *initNode = nullptr;
        TType *type             = new TType(publicType);
        if (executeInitializer(identifierLocation, identifier, type, initializer, &initNode))
        {
            //
            // build the intermediate representation
            //
            if (initNode)
            {
                declarationOut->appendDeclarator(initNode);
            }
        }
    }
    
    void TParseContext::parseArrayInitDeclarator(const TPublicType &elementType,
                                                 const TSourceLoc &identifierLocation,
                                                 const ImmutableString &identifier,
                                                 const TSourceLoc &indexLocation,
                                                 const TVector<unsigned int> &arraySizes,
                                                 const TSourceLoc &initLocation,
                                                 TIntermTyped *initializer,
                                                 TIntermDeclaration *declarationOut)
    {
        // If the declaration starting this declarator list was empty (example: int,), some checks were
        // not performed.
        if (mDeferredNonEmptyDeclarationErrorCheck)
        {
            nonEmptyDeclarationErrorCheck(elementType, identifierLocation);
            mDeferredNonEmptyDeclarationErrorCheck = false;
        }
    
        checkDeclaratorLocationIsNotSpecified(identifierLocation, elementType);
    
        checkIsValidTypeAndQualifierForArray(indexLocation, elementType);
    
        TType *arrayType = new TType(elementType);
        arrayType->makeArrays(arraySizes);
    
        // initNode will correspond to the whole of "b[n] = initializer".
        TIntermBinary *initNode = nullptr;
        if (executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode))
        {
            if (initNode)
            {
                declarationOut->appendDeclarator(initNode);
            }
        }
    }
    
    TIntermNode *TParseContext::addEmptyStatement(const TSourceLoc &location)
    {
        // It's simpler to parse an empty statement as a constant expression rather than having a
        // different type of node just for empty statements, that will be pruned from the AST anyway.
        TIntermNode *node = CreateZeroNode(TType(EbtInt, EbpMedium));
        node->setLine(location);
        return node;
    }
    
    void TParseContext::setAtomicCounterBindingDefaultOffset(const TPublicType &publicType,
                                                             const TSourceLoc &location)
    {
        const TLayoutQualifier &layoutQualifier = publicType.layoutQualifier;
        checkAtomicCounterBindingIsValid(location, layoutQualifier.binding);
        if (layoutQualifier.binding == -1 || layoutQualifier.offset == -1)
        {
            error(location, "Requires both binding and offset", "layout");
            return;
        }
        mAtomicCounterBindingStates[layoutQualifier.binding].setDefaultOffset(layoutQualifier.offset);
    }
    
    void TParseContext::parseDefaultPrecisionQualifier(const TPrecision precision,
                                                       const TPublicType &type,
                                                       const TSourceLoc &loc)
    {
        if ((precision == EbpHigh) && (getShaderType() == GL_FRAGMENT_SHADER) &&
            !getFragmentPrecisionHigh())
        {
            error(loc, "precision is not supported in fragment shader", "highp");
        }
    
        if (!CanSetDefaultPrecisionOnType(type))
        {
            error(loc, "illegal type argument for default precision qualifier",
                  getBasicString(type.getBasicType()));
            return;
        }
        symbolTable.setDefaultPrecision(type.getBasicType(), precision);
    }
    
    bool TParseContext::checkPrimitiveTypeMatchesTypeQualifier(const TTypeQualifier &typeQualifier)
    {
        switch (typeQualifier.layoutQualifier.primitiveType)
        {
            case EptLines:
            case EptLinesAdjacency:
            case EptTriangles:
            case EptTrianglesAdjacency:
                return typeQualifier.qualifier == EvqGeometryIn;
    
            case EptLineStrip:
            case EptTriangleStrip:
                return typeQualifier.qualifier == EvqGeometryOut;
    
            case EptPoints:
                return true;
    
            default:
                UNREACHABLE();
                return false;
        }
    }
    
    void TParseContext::setGeometryShaderInputArraySize(unsigned int inputArraySize,
                                                        const TSourceLoc &line)
    {
        if (!symbolTable.setGlInArraySize(inputArraySize))
        {
            error(line,
                  "Array size or input primitive declaration doesn't match the size of earlier sized "
                  "array inputs.",
                  "layout");
        }
        mGeometryInputArraySize = inputArraySize;
    }
    
    bool TParseContext::parseGeometryShaderInputLayoutQualifier(const TTypeQualifier &typeQualifier)
    {
        ASSERT(typeQualifier.qualifier == EvqGeometryIn);
    
        const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier;
    
        if (layoutQualifier.maxVertices != -1)
        {
            error(typeQualifier.line,
                  "max_vertices can only be declared in 'out' layout in a geometry shader", "layout");
            return false;
        }
    
        // Set mGeometryInputPrimitiveType if exists
        if (layoutQualifier.primitiveType != EptUndefined)
        {
            if (!checkPrimitiveTypeMatchesTypeQualifier(typeQualifier))
            {
                error(typeQualifier.line, "invalid primitive type for 'in' layout", "layout");
                return false;
            }
    
            if (mGeometryShaderInputPrimitiveType == EptUndefined)
            {
                mGeometryShaderInputPrimitiveType = layoutQualifier.primitiveType;
                setGeometryShaderInputArraySize(
                    GetGeometryShaderInputArraySize(mGeometryShaderInputPrimitiveType),
                    typeQualifier.line);
            }
            else if (mGeometryShaderInputPrimitiveType != layoutQualifier.primitiveType)
            {
                error(typeQualifier.line, "primitive doesn't match earlier input primitive declaration",
                      "layout");
                return false;
            }
    
            // Size any implicitly sized arrays that have already been declared.
            for (TType *type : mDeferredArrayTypesToSize)
            {
                type->sizeOutermostUnsizedArray(
                    symbolTable.getGlInVariableWithArraySize()->getType().getOutermostArraySize());
            }
            mDeferredArrayTypesToSize.clear();
        }
    
        // Set mGeometryInvocations if exists
        if (layoutQualifier.invocations > 0)
        {
            if (mGeometryShaderInvocations == 0)
            {
                mGeometryShaderInvocations = layoutQualifier.invocations;
            }
            else if (mGeometryShaderInvocations != layoutQualifier.invocations)
            {
                error(typeQualifier.line, "invocations contradicts to the earlier declaration",
                      "layout");
                return false;
            }
        }
    
        return true;
    }
    
    bool TParseContext::parseGeometryShaderOutputLayoutQualifier(const TTypeQualifier &typeQualifier)
    {
        ASSERT(typeQualifier.qualifier == EvqGeometryOut);
    
        const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier;
    
        if (layoutQualifier.invocations > 0)
        {
            error(typeQualifier.line,
                  "invocations can only be declared in 'in' layout in a geometry shader", "layout");
            return false;
        }
    
        // Set mGeometryOutputPrimitiveType if exists
        if (layoutQualifier.primitiveType != EptUndefined)
        {
            if (!checkPrimitiveTypeMatchesTypeQualifier(typeQualifier))
            {
                error(typeQualifier.line, "invalid primitive type for 'out' layout", "layout");
                return false;
            }
    
            if (mGeometryShaderOutputPrimitiveType == EptUndefined)
            {
                mGeometryShaderOutputPrimitiveType = layoutQualifier.primitiveType;
            }
            else if (mGeometryShaderOutputPrimitiveType != layoutQualifier.primitiveType)
            {
                error(typeQualifier.line,
                      "primitive doesn't match earlier output primitive declaration", "layout");
                return false;
            }
        }
    
        // Set mGeometryMaxVertices if exists
        if (layoutQualifier.maxVertices > -1)
        {
            if (mGeometryShaderMaxVertices == -1)
            {
                mGeometryShaderMaxVertices = layoutQualifier.maxVertices;
            }
            else if (mGeometryShaderMaxVertices != layoutQualifier.maxVertices)
            {
                error(typeQualifier.line, "max_vertices contradicts to the earlier declaration",
                      "layout");
                return false;
            }
        }
    
        return true;
    }
    
    bool TParseContext::parseTessControlShaderOutputLayoutQualifier(const TTypeQualifier &typeQualifier)
    {
        ASSERT(typeQualifier.qualifier == EvqTessControlOut);
    
        const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier;
    
        if (layoutQualifier.vertices == 0)
        {
            error(typeQualifier.line, "No vertices specified", "layout");
            return false;
        }
    
        // Set mTessControlShaderOutputVertices if exists
        if (mTessControlShaderOutputVertices == 0)
        {
            mTessControlShaderOutputVertices = layoutQualifier.vertices;
    
            // Size any implicitly sized arrays that have already been declared.
            for (TType *type : mDeferredArrayTypesToSize)
            {
                type->sizeOutermostUnsizedArray(mTessControlShaderOutputVertices);
            }
            mDeferredArrayTypesToSize.clear();
        }
        else
        {
            error(typeQualifier.line, "Duplicated vertices specified", "layout");
        }
        return true;
    }
    
    bool TParseContext::parseTessEvaluationShaderInputLayoutQualifier(
        const TTypeQualifier &typeQualifier)
    {
        ASSERT(typeQualifier.qualifier == EvqTessEvaluationIn);
    
        const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier;
    
        // Set mTessEvaluationShaderInputPrimitiveType if exists
        if (layoutQualifier.tesPrimitiveType != EtetUndefined)
        {
            if (mTessEvaluationShaderInputPrimitiveType == EtetUndefined)
            {
                mTessEvaluationShaderInputPrimitiveType = layoutQualifier.tesPrimitiveType;
            }
            else
            {
                error(typeQualifier.line, "Duplicated primitive type declaration", "layout");
            }
        }
        // Set mTessEvaluationShaderVertexSpacingType if exists
        if (layoutQualifier.tesVertexSpacingType != EtetUndefined)
        {
            if (mTessEvaluationShaderInputVertexSpacingType == EtetUndefined)
            {
                mTessEvaluationShaderInputVertexSpacingType = layoutQualifier.tesVertexSpacingType;
            }
            else
            {
                error(typeQualifier.line, "Duplicated vertex spacing declaration", "layout");
            }
        }
        // Set mTessEvaluationShaderInputOrderingType if exists
        if (layoutQualifier.tesOrderingType != EtetUndefined)
        {
            if (mTessEvaluationShaderInputOrderingType == EtetUndefined)
            {
                mTessEvaluationShaderInputOrderingType = layoutQualifier.tesOrderingType;
            }
            else
            {
                error(typeQualifier.line, "Duplicated ordering declaration", "layout");
            }
        }
        // Set mTessEvaluationShaderInputPointType if exists
        if (layoutQualifier.tesPointType != EtetUndefined)
        {
            if (mTessEvaluationShaderInputPointType == EtetUndefined)
            {
                mTessEvaluationShaderInputPointType = layoutQualifier.tesPointType;
            }
            else
            {
                error(typeQualifier.line, "Duplicated point type declaration", "layout");
            }
        }
    
        return true;
    }
    
    void TParseContext::parseGlobalLayoutQualifier(const TTypeQualifierBuilder &typeQualifierBuilder)
    {
        TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
        const TLayoutQualifier layoutQualifier = typeQualifier.layoutQualifier;
    
        checkInvariantVariableQualifier(typeQualifier.invariant, typeQualifier.qualifier,
                                        typeQualifier.line);
    
        // It should never be the case, but some strange parser errors can send us here.
        if (layoutQualifier.isEmpty())
        {
            error(typeQualifier.line, "Error during layout qualifier parsing.", "?");
            return;
        }
    
        if (!layoutQualifier.isCombinationValid())
        {
            error(typeQualifier.line, "invalid layout qualifier combination", "layout");
            return;
        }
    
        checkIndexIsNotSpecified(typeQualifier.line, layoutQualifier.index);
    
        checkBindingIsNotSpecified(typeQualifier.line, layoutQualifier.binding);
    
        checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line);
    
        checkInternalFormatIsNotSpecified(typeQualifier.line, layoutQualifier.imageInternalFormat);
    
        checkDepthIsNotSpecified(typeQualifier.line, layoutQualifier.depth);
    
        checkYuvIsNotSpecified(typeQualifier.line, layoutQualifier.yuv);
    
        checkOffsetIsNotSpecified(typeQualifier.line, layoutQualifier.offset);
    
        checkStd430IsForShaderStorageBlock(typeQualifier.line, layoutQualifier.blockStorage,
                                           typeQualifier.qualifier);
    
        checkAdvancedBlendEquationsNotSpecified(
            typeQualifier.line, layoutQualifier.advancedBlendEquations, typeQualifier.qualifier);
    
        if (typeQualifier.qualifier != EvqFragmentIn)
        {
            checkEarlyFragmentTestsIsNotSpecified(typeQualifier.line,
                                                  layoutQualifier.earlyFragmentTests);
        }
    
        if (typeQualifier.qualifier == EvqComputeIn)
        {
            if (mComputeShaderLocalSizeDeclared &&
                !layoutQualifier.isLocalSizeEqual(mComputeShaderLocalSize))
            {
                error(typeQualifier.line, "Work group size does not match the previous declaration",
                      "layout");
                return;
            }
    
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line, "in type qualifier supported in GLSL ES 3.10 only", "layout");
                return;
            }
    
            if (!layoutQualifier.localSize.isAnyValueSet())
            {
                error(typeQualifier.line, "No local work group size specified", "layout");
                return;
            }
    
            const TVariable *maxComputeWorkGroupSize = static_cast<const TVariable *>(
                symbolTable.findBuiltIn(ImmutableString("gl_MaxComputeWorkGroupSize"), mShaderVersion));
    
            const TConstantUnion *maxComputeWorkGroupSizeData =
                maxComputeWorkGroupSize->getConstPointer();
    
            for (size_t i = 0u; i < layoutQualifier.localSize.size(); ++i)
            {
                if (layoutQualifier.localSize[i] != -1)
                {
                    mComputeShaderLocalSize[i]             = layoutQualifier.localSize[i];
                    const int maxComputeWorkGroupSizeValue = maxComputeWorkGroupSizeData[i].getIConst();
                    if (mComputeShaderLocalSize[i] < 1 ||
                        mComputeShaderLocalSize[i] > maxComputeWorkGroupSizeValue)
                    {
                        std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
                        reasonStream << "invalid value: Value must be at least 1 and no greater than "
                                     << maxComputeWorkGroupSizeValue;
                        const std::string &reason = reasonStream.str();
    
                        error(typeQualifier.line, reason.c_str(), getWorkGroupSizeString(i));
                        return;
                    }
                }
            }
    
            mComputeShaderLocalSizeDeclared = true;
        }
        else if (typeQualifier.qualifier == EvqGeometryIn)
        {
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line, "in type qualifier supported in GLSL ES 3.10 only", "layout");
                return;
            }
    
            if (!parseGeometryShaderInputLayoutQualifier(typeQualifier))
            {
                return;
            }
        }
        else if (typeQualifier.qualifier == EvqGeometryOut)
        {
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line, "out type qualifier supported in GLSL ES 3.10 only",
                      "layout");
                return;
            }
    
            if (!parseGeometryShaderOutputLayoutQualifier(typeQualifier))
            {
                return;
            }
        }
        else if (anyMultiviewExtensionAvailable() && typeQualifier.qualifier == EvqVertexIn)
        {
            // This error is only specified in WebGL, but tightens unspecified behavior in the native
            // specification.
            if (mNumViews != -1 && layoutQualifier.numViews != mNumViews)
            {
                error(typeQualifier.line, "Number of views does not match the previous declaration",
                      "layout");
                return;
            }
    
            if (layoutQualifier.numViews == -1)
            {
                error(typeQualifier.line, "No num_views specified", "layout");
                return;
            }
    
            if (layoutQualifier.numViews > mMaxNumViews)
            {
                error(typeQualifier.line, "num_views greater than the value of GL_MAX_VIEWS_OVR",
                      "layout");
                return;
            }
    
            mNumViews = layoutQualifier.numViews;
        }
        else if (typeQualifier.qualifier == EvqFragmentIn)
        {
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line,
                      "in type qualifier without variable declaration supported in GLSL ES 3.10 and "
                      "after",
                      "layout");
                return;
            }
    
            if (!layoutQualifier.earlyFragmentTests)
            {
                error(typeQualifier.line,
                      "only early_fragment_tests is allowed as layout qualifier when not declaring a "
                      "variable",
                      "layout");
                return;
            }
    
            mEarlyFragmentTestsSpecified = true;
        }
        else if (typeQualifier.qualifier == EvqFragmentOut)
        {
            if (mShaderVersion < 320 && !isExtensionEnabled(TExtension::KHR_blend_equation_advanced))
            {
                error(typeQualifier.line,
                      "out type qualifier without variable declaration is supported in GLSL ES 3.20,"
                      " or if GL_KHR_blend_equation_advanced is enabled",
                      "layout");
                return;
            }
    
            if (!layoutQualifier.advancedBlendEquations.any())
            {
                error(typeQualifier.line,
                      "only blend equations are allowed as layout qualifier when not declaring a "
                      "variable",
                      "layout");
                return;
            }
    
            errorIfPLSDeclared(typeQualifier.line, PLSIllegalOperations::EnableAdvancedBlendEquation);
            mAdvancedBlendEquations |= layoutQualifier.advancedBlendEquations;
        }
        else if (typeQualifier.qualifier == EvqTessControlOut)
        {
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line, "out type qualifier supported in GLSL ES 3.10 and after",
                      "layout");
                return;
            }
    
            if (!parseTessControlShaderOutputLayoutQualifier(typeQualifier))
            {
                return;
            }
        }
        else if (typeQualifier.qualifier == EvqTessEvaluationIn)
        {
            if (mShaderVersion < 310)
            {
                error(typeQualifier.line, "in type qualifier supported in GLSL ES 3.10 and after",
                      "layout");
                return;
            }
    
            if (!parseTessEvaluationShaderInputLayoutQualifier(typeQualifier))
            {
                return;
            }
        }
        else
        {
            if (!checkWorkGroupSizeIsNotSpecified(typeQualifier.line, layoutQualifier))
            {
                return;
            }
    
            if (typeQualifier.qualifier != EvqUniform && typeQualifier.qualifier != EvqBuffer)
            {
                error(typeQualifier.line, "invalid qualifier: global layout can only be set for blocks",
                      getQualifierString(typeQualifier.qualifier));
                return;
            }
    
            if (mShaderVersion < 300)
            {
                error(typeQualifier.line, "layout qualifiers supported in GLSL ES 3.00 and after",
                      "layout");
                return;
            }
    
            checkLocationIsNotSpecified(typeQualifier.line, layoutQualifier);
    
            if (layoutQualifier.matrixPacking != EmpUnspecified)
            {
                if (typeQualifier.qualifier == EvqUniform)
                {
                    mDefaultUniformMatrixPacking = layoutQualifier.matrixPacking;
                }
                else if (typeQualifier.qualifier == EvqBuffer)
                {
                    mDefaultBufferMatrixPacking = layoutQualifier.matrixPacking;
                }
            }
    
            if (layoutQualifier.blockStorage != EbsUnspecified)
            {
                if (typeQualifier.qualifier == EvqUniform)
                {
                    mDefaultUniformBlockStorage = layoutQualifier.blockStorage;
                }
                else if (typeQualifier.qualifier == EvqBuffer)
                {
                    mDefaultBufferBlockStorage = layoutQualifier.blockStorage;
                }
            }
        }
    }
    
    TIntermFunctionPrototype *TParseContext::createPrototypeNodeFromFunction(
        const TFunction &function,
        const TSourceLoc &location,
        bool insertParametersToSymbolTable)
    {
        checkIsNotReserved(location, function.name());
    
        TIntermFunctionPrototype *prototype = new TIntermFunctionPrototype(&function);
        prototype->setLine(location);
    
        for (size_t i = 0; i < function.getParamCount(); i++)
        {
            const TVariable *param = function.getParam(i);
    
            // If the parameter has no name, it's not an error, just don't add it to symbol table (could
            // be used for unused args).
            if (param->symbolType() != SymbolType::Empty)
            {
                if (insertParametersToSymbolTable)
                {
                    if (!symbolTable.declare(const_cast<TVariable *>(param)))
                    {
                        error(location, "redefinition", param->name());
                    }
                }
                // Unsized type of a named parameter should have already been checked and sanitized.
                ASSERT(!param->getType().isUnsizedArray());
            }
        }
        return prototype;
    }
    
    TIntermFunctionPrototype *TParseContext::addFunctionPrototypeDeclaration(
        const TFunction &parsedFunction,
        const TSourceLoc &location)
    {
        // Note: function found from the symbol table could be the same as parsedFunction if this is the
        // first declaration. Either way the instance in the symbol table is used to track whether the
        // function is declared multiple times.
        bool hadPrototypeDeclaration = false;
        const TFunction *function    = symbolTable.markFunctionHasPrototypeDeclaration(
            parsedFunction.getMangledName(), &hadPrototypeDeclaration);
    
        if (hadPrototypeDeclaration && mShaderVersion == 100)
        {
            // ESSL 1.00.17 section 4.2.7.
            // Doesn't apply to ESSL 3.00.4: see section 4.2.3.
            error(location, "duplicate function prototype declarations are not allowed", "function");
        }
    
        TIntermFunctionPrototype *prototype =
            createPrototypeNodeFromFunction(*function, location, false);
    
        symbolTable.pop();
    
        if (!symbolTable.atGlobalLevel())
        {
            // ESSL 3.00.4 section 4.2.4.
            error(location, "local function prototype declarations are not allowed", "function");
        }
    
        return prototype;
    }
    
    TIntermFunctionDefinition *TParseContext::addFunctionDefinition(
        TIntermFunctionPrototype *functionPrototype,
        TIntermBlock *functionBody,
        const TSourceLoc &location)
    {
        // Undo push at end of parseFunctionDefinitionHeader() below for ESSL1.00 case
        if (mFunctionBodyNewScope)
        {
            mFunctionBodyNewScope = false;
            symbolTable.pop();
        }
    
        // Check that non-void functions have at least one return statement.
        if (mCurrentFunctionType->getBasicType() != EbtVoid && !mFunctionReturnsValue)
        {
            error(location,
                  "function does not return a value:", functionPrototype->getFunction()->name());
        }
    
        if (functionBody == nullptr)
        {
            functionBody = new TIntermBlock();
            functionBody->setLine(location);
        }
        TIntermFunctionDefinition *functionNode =
            new TIntermFunctionDefinition(functionPrototype, functionBody);
        functionNode->setLine(location);
    
        symbolTable.pop();
        return functionNode;
    }
    
    void TParseContext::parseFunctionDefinitionHeader(const TSourceLoc &location,
                                                      const TFunction *function,
                                                      TIntermFunctionPrototype **prototypeOut)
    {
        ASSERT(function);
    
        bool wasDefined = false;
        function        = symbolTable.setFunctionParameterNamesFromDefinition(function, &wasDefined);
        if (wasDefined)
        {
            error(location, "function already has a body", function->name());
        }
    
        // Remember the return type for later checking for return statements.
        mCurrentFunctionType  = &(function->getReturnType());
        mFunctionReturnsValue = false;
    
        *prototypeOut = createPrototypeNodeFromFunction(*function, location, true);
        setLoopNestingLevel(0);
    
        // ESSL 1.00 spec allows for variable in function body to redefine parameter
        if (IsSpecWithFunctionBodyNewScope(mShaderSpec, mShaderVersion))
        {
            mFunctionBodyNewScope = true;
            symbolTable.push();
        }
    }
    
    TFunction *TParseContext::parseFunctionDeclarator(const TSourceLoc &location, TFunction *function)
    {
        //
        // We don't know at this point whether this is a function definition or a prototype.
        // The definition production code will check for redefinitions.
        // In the case of ESSL 1.00 the prototype production code will also check for redeclarations.
        //
    
        for (size_t i = 0u; i < function->getParamCount(); ++i)
        {
            const TVariable *param = function->getParam(i);
            const TType &paramType = param->getType();
    
            checkPrecisionSpecified(location, paramType.getPrecision(), paramType.getBasicType());
        }
    
        if (getShaderVersion() >= 300)
        {
            if (symbolTable.isUnmangledBuiltInName(function->name(), getShaderVersion(),
                                                   extensionBehavior()))
            {
                // With ESSL 3.00 and above, names of built-in functions cannot be redeclared as
                // functions. Therefore overloading or redefining builtin functions is an error.
                error(location, "Name of a built-in function cannot be redeclared as function",
                      function->name());
            }
        }
        else
        {
            // ESSL 1.00.17 section 4.2.6: built-ins can be overloaded but not redefined. We assume that
            // this applies to redeclarations as well.
            const TSymbol *builtIn =
                symbolTable.findBuiltIn(function->getMangledName(), getShaderVersion());
            if (builtIn)
            {
                error(location, "built-in functions cannot be redefined", function->name());
            }
        }
    
        // Return types and parameter qualifiers must match in all redeclarations, so those are checked
        // here.
        const TFunction *prevDec =
            static_cast<const TFunction *>(symbolTable.findGlobal(function->getMangledName()));
        if (prevDec)
        {
            if (prevDec->getReturnType() != function->getReturnType())
            {
                error(location, "function must have the same return type in all of its declarations",
                      function->getReturnType().getBasicString());
            }
            for (size_t i = 0; i < prevDec->getParamCount(); ++i)
            {
                if (prevDec->getParam(i)->getType().getQualifier() !=
                    function->getParam(i)->getType().getQualifier())
                {
                    error(location,
                          "function must have the same parameter qualifiers in all of its declarations",
                          function->getParam(i)->getType().getQualifierString());
                }
            }
        }
    
        // Check for previously declared variables using the same name.
        const TSymbol *prevSym   = symbolTable.find(function->name(), getShaderVersion());
        bool insertUnmangledName = true;
        if (prevSym)
        {
            if (!prevSym->isFunction())
            {
                error(location, "redefinition of a function", function->name());
            }
            insertUnmangledName = false;
        }
        // Parsing is at the inner scope level of the function's arguments and body statement at this
        // point, but declareUserDefinedFunction takes care of declaring the function at the global
        // scope.
        symbolTable.declareUserDefinedFunction(function, insertUnmangledName);
    
        // Raise error message if main function takes any parameters or return anything other than void
        if (function->isMain())
        {
            if (function->getParamCount() > 0)
            {
                error(location, "function cannot take any parameter(s)", "main");
            }
            if (function->getReturnType().getBasicType() != EbtVoid)
            {
                error(location, "main function cannot return a value",
                      function->getReturnType().getBasicString());
            }
        }
    
        mDeclaringMain = function->isMain();
    
        //
        // If this is a redeclaration, it could also be a definition, in which case, we want to use the
        // variable names from this one, and not the one that's
        // being redeclared.  So, pass back up this declaration, not the one in the symbol table.
        //
        return function;
    }
    
    TFunction *TParseContext::parseFunctionHeader(const TPublicType &type,
                                                  const ImmutableString &name,
                                                  const TSourceLoc &location)
    {
        if (type.qualifier != EvqGlobal && type.qualifier != EvqTemporary)
        {
            error(location, "no qualifiers allowed for function return",
                  getQualifierString(type.qualifier));
        }
        if (!type.layoutQualifier.isEmpty())
        {
            error(location, "no qualifiers allowed for function return", "layout");
        }
        // make sure an opaque type is not involved as well...
        std::string reason(getBasicString(type.getBasicType()));
        reason += "s can't be function return values";
        checkIsNotOpaqueType(location, type.typeSpecifierNonArray, reason.c_str());
        if (mShaderVersion < 300)
        {
            // Array return values are forbidden, but there's also no valid syntax for declaring array
            // return values in ESSL 1.00.
            ASSERT(!type.isArray() || mDiagnostics->numErrors() > 0);
    
            if (type.isStructureContainingArrays())
            {
                // ESSL 1.00.17 section 6.1 Function Definitions
                TInfoSinkBase typeString;
                typeString << TType(type);
                error(location, "structures containing arrays can't be function return values",
                      typeString.c_str());
            }
        }
    
        // Add the function as a prototype after parsing it (we do not support recursion)
        return new TFunction(&symbolTable, name, SymbolType::UserDefined, new TType(type), false);
    }
    
    TFunctionLookup *TParseContext::addNonConstructorFunc(const ImmutableString &name,
                                                          const TSymbol *symbol)
    {
        return TFunctionLookup::CreateFunctionCall(name, symbol);
    }
    
    TFunctionLookup *TParseContext::addConstructorFunc(const TPublicType &publicType)
    {
        if (mShaderVersion < 300 && publicType.isArray())
        {
            error(publicType.getLine(), "array constructor supported in GLSL ES 3.00 and above only",
                  "[]");
        }
        if (publicType.isStructSpecifier())
        {
            error(publicType.getLine(), "constructor can't be a structure definition",
                  getBasicString(publicType.getBasicType()));
        }
    
        TType *type = new TType(publicType);
        if (!type->canBeConstructed())
        {
            error(publicType.getLine(), "cannot construct this type",
                  getBasicString(publicType.getBasicType()));
            type->setBasicType(EbtFloat);
        }
        return TFunctionLookup::CreateConstructor(type);
    }
    
    void TParseContext::checkIsNotUnsizedArray(const TSourceLoc &line,
                                               const char *errorMessage,
                                               const ImmutableString &token,
                                               TType *arrayType)
    {
        if (arrayType->isUnsizedArray())
        {
            error(line, errorMessage, token);
            arrayType->sizeUnsizedArrays(angle::Span<const unsigned int>());
        }
    }
    
    TParameter TParseContext::parseParameterDeclarator(const TPublicType &type,
                                                       const ImmutableString &name,
                                                       const TSourceLoc &nameLoc)
    {
        if (!name.empty())
        {
            if (type.getBasicType() == EbtVoid)
            {
                error(nameLoc, "illegal use of type 'void'", name);
            }
        }
        if (type.isStructSpecifier())
        {
            // ESSL 3.00.6 section 12.10.
            error(nameLoc, "Function parameter type cannot be a structure definition", name);
        }
        checkIsNotReserved(nameLoc, name);
        TParameter param{name.data(), type};
        if (param.type.isUnsizedArray())
        {
            error(nameLoc, "function parameter array must specify a size", name);
            param.type.sizeUnsizedArrays();
        }
        return param;
    }
    
    TParameter TParseContext::parseParameterArrayDeclarator(const TPublicType &elementType,
                                                            const ImmutableString &name,
                                                            const TSourceLoc &nameLoc,
                                                            TVector<unsigned int> *arraySizes,
                                                            const TSourceLoc &arrayLoc)
    {
        checkArrayElementIsNotArray(arrayLoc, elementType);
        TPublicType arrayType{elementType};
        arrayType.makeArrays(arraySizes);
        return parseParameterDeclarator(arrayType, name, nameLoc);
    }
    
    bool TParseContext::checkUnsizedArrayConstructorArgumentDimensionality(
        const TIntermSequence &arguments,
        TType type,
        const TSourceLoc &line)
    {
        if (arguments.empty())
        {
            error(line, "implicitly sized array constructor must have at least one argument", "[]");
            return false;
        }
        for (TIntermNode *arg : arguments)
        {
            const TIntermTyped *element = arg->getAsTyped();
            ASSERT(element);
            if (element->getType().isUnsizedArray())
            {
                error(line, "constructing from an unsized array", "constructor");
                return false;
            }
            size_t dimensionalityFromElement = element->getType().getNumArraySizes() + 1u;
            if (dimensionalityFromElement > type.getNumArraySizes())
            {
                error(line, "constructing from a non-dereferenced array", "constructor");
                return false;
            }
            else if (dimensionalityFromElement < type.getNumArraySizes())
            {
                if (dimensionalityFromElement == 1u)
                {
                    error(line, "implicitly sized array of arrays constructor argument is not an array",
                          "constructor");
                }
                else
                {
                    error(line,
                          "implicitly sized array of arrays constructor argument dimensionality is too "
                          "low",
                          "constructor");
                }
                return false;
            }
        }
        return true;
    }
    
    // This function is used to test for the correctness of the parameters passed to various constructor
    // functions and also convert them to the right datatype if it is allowed and required.
    //
    // Returns a node to add to the tree regardless of if an error was generated or not.
    //
    TIntermTyped *TParseContext::addConstructor(TFunctionLookup *fnCall, const TSourceLoc &line)
    {
        TType type                 = fnCall->constructorType();
        TIntermSequence &arguments = fnCall->arguments();
        if (type.isUnsizedArray())
        {
            if (!checkUnsizedArrayConstructorArgumentDimensionality(arguments, type, line))
            {
                type.sizeUnsizedArrays(angle::Span<const unsigned int>());
                return CreateZeroNode(type);
            }
            TIntermTyped *firstElement = arguments.at(0)->getAsTyped();
            ASSERT(firstElement);
            if (type.getOutermostArraySize() == 0u)
            {
                type.sizeOutermostUnsizedArray(static_cast<unsigned int>(arguments.size()));
            }
            for (size_t i = 0; i < firstElement->getType().getNumArraySizes(); ++i)
            {
                if (type.getArraySizes()[i] == 0u)
                {
                    type.setArraySize(i, firstElement->getType().getArraySizes()[i]);
                }
            }
            ASSERT(!type.isUnsizedArray());
        }
    
        if (!checkConstructorArguments(line, arguments, type))
        {
            return CreateZeroNode(type);
        }
    
        TIntermAggregate *constructorNode = TIntermAggregate::CreateConstructor(type, &arguments);
        constructorNode->setLine(line);
    
        return constructorNode->fold(mDiagnostics);
    }
    
    //
    // Interface/uniform blocks
    TIntermDeclaration *TParseContext::addInterfaceBlock(
        const TTypeQualifierBuilder &typeQualifierBuilder,
        const TSourceLoc &nameLine,
        const ImmutableString &blockName,
        TFieldList *fieldList,
        const ImmutableString &instanceName,
        const TSourceLoc &instanceLine,
        const TVector<unsigned int> *arraySizes,
        const TSourceLoc &arraySizesLine)
    {
        checkDoesNotHaveTooManyFields(blockName, fieldList, nameLine);
    
        // Ensure there are no duplicate field names
        checkDoesNotHaveDuplicateFieldNames(fieldList, nameLine);
    
        const bool isGLPerVertex = blockName == "gl_PerVertex";
        // gl_PerVertex is allowed to be redefined and therefore not reserved
        if (!isGLPerVertex)
        {
            checkIsNotReserved(nameLine, blockName);
        }
    
        TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
    
        const bool isUniformOrBuffer =
            typeQualifier.qualifier == EvqUniform || typeQualifier.qualifier == EvqBuffer;
        const bool isShaderIoBlock = IsShaderIoBlock(typeQualifier.qualifier);
    
        if (mShaderVersion < 310 && typeQualifier.qualifier != EvqUniform)
        {
            error(typeQualifier.line,
                  "invalid qualifier: interface blocks must be uniform in version lower than GLSL ES "
                  "3.10",
                  getQualifierString(typeQualifier.qualifier));
        }
        else if (typeQualifier.qualifier == EvqPatchOut)
        {
            if ((!isExtensionEnabled(TExtension::EXT_tessellation_shader) &&
                 !isExtensionEnabled(TExtension::OES_tessellation_shader) && mShaderVersion < 320) ||
                mShaderType != GL_TESS_CONTROL_SHADER)
            {
                error(typeQualifier.line,
                      "invalid qualifier: 'patch out' requires a tessellation control shader",
                      getQualifierString(typeQualifier.qualifier));
            }
        }
        else if (typeQualifier.qualifier == EvqPatchIn)
        {
            if ((!isExtensionEnabled(TExtension::EXT_tessellation_shader) &&
                 !isExtensionEnabled(TExtension::OES_tessellation_shader) && mShaderVersion < 320) ||
                mShaderType != GL_TESS_EVALUATION_SHADER)
            {
                error(typeQualifier.line,
                      "invalid qualifier: 'patch in' requires a tessellation evaluation shader",
                      getQualifierString(typeQualifier.qualifier));
            }
        }
        else if (typeQualifier.qualifier != EvqUniform && typeQualifier.qualifier != EvqBuffer)
        {
            if (isShaderIoBlock)
            {
                if (!isExtensionEnabled(TExtension::OES_shader_io_blocks) &&
                    !isExtensionEnabled(TExtension::EXT_shader_io_blocks) &&
                    !isExtensionEnabled(TExtension::OES_geometry_shader) &&
                    !isExtensionEnabled(TExtension::EXT_geometry_shader) && mShaderVersion < 320)
                {
                    error(typeQualifier.line,
                          "invalid qualifier: shader IO blocks need shader io block extension",
                          getQualifierString(typeQualifier.qualifier));
                }
    
                // Both inputs and outputs of tessellation control shaders must be arrays.
                // For tessellation evaluation shaders, only inputs must necessarily be arrays.
                const bool isTCS = mShaderType == GL_TESS_CONTROL_SHADER;
                const bool isTESIn =
                    mShaderType == GL_TESS_EVALUATION_SHADER && IsShaderIn(typeQualifier.qualifier);
                if (arraySizes == nullptr && (isTCS || isTESIn))
                {
                    error(typeQualifier.line, "type must be an array", blockName);
                }
            }
            else
            {
                error(typeQualifier.line,
                      "invalid qualifier: interface blocks must be uniform or buffer",
                      getQualifierString(typeQualifier.qualifier));
            }
        }
    
        if (typeQualifier.invariant)
        {
            error(typeQualifier.line, "invalid qualifier on interface block", "invariant");
        }
    
        if (typeQualifier.qualifier != EvqBuffer)
        {
            checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line);
        }
    
        // Verify array sizes
        if (arraySizes)
        {
            if (isUniformOrBuffer)
            {
                if (arraySizes->size() == 0)
                {
                    error(arraySizesLine, "unsized arrays are not allowed with interface blocks", "");
                }
                if (arraySizes->size() > 1)
                {
                    error(arraySizesLine, "array of arrays are not allowed with interface blocks", "");
                }
            }
            else if (isShaderIoBlock)
            {
                size_t arrayDimensions = arraySizes->size();
    
                // Geometry shader inputs have a level arrayness that must be ignored.
                if (mShaderType == GL_GEOMETRY_SHADER_EXT && IsVaryingIn(typeQualifier.qualifier))
                {
                    ASSERT(arrayDimensions > 0);
                    --arrayDimensions;
    
                    // Validate that the array size of input matches the geometry layout
                    // declaration, if not automatic (specified as []).
                    const unsigned int geometryDim = arraySizes->back();
                    if (geometryDim > 0 && geometryDim != mGeometryInputArraySize)
                    {
                        error(arraySizesLine,
                              "geometry shader input block array size inconsistent "
                              "with primitive",
                              "");
                    }
                }
    
                if (arrayDimensions > 1)
                {
                    error(arraySizesLine, "array of arrays are not allowed with I/O blocks", "");
                }
            }
        }
        else if (isShaderIoBlock && mShaderType == GL_GEOMETRY_SHADER_EXT &&
                 IsVaryingIn(typeQualifier.qualifier))
        {
            error(arraySizesLine, "geometry shader input blocks must be an array", "");
        }
    
        checkIndexIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.index);
    
        if (mShaderVersion < 310)
        {
            checkBindingIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.binding);
        }
        else
        {
            unsigned int arraySize =
                arraySizes == nullptr || arraySizes->empty() ? 0 : (*arraySizes)[0];
            checkBlockBindingIsValid(typeQualifier.line, typeQualifier.qualifier,
                                     typeQualifier.layoutQualifier.binding, arraySize);
        }
    
        checkDepthIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.depth);
        checkYuvIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.yuv);
        checkEarlyFragmentTestsIsNotSpecified(typeQualifier.line,
                                              typeQualifier.layoutQualifier.earlyFragmentTests);
        checkNoncoherentIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.noncoherent);
    
        TLayoutQualifier blockLayoutQualifier = typeQualifier.layoutQualifier;
        if (!IsShaderIoBlock(typeQualifier.qualifier) && typeQualifier.qualifier != EvqPatchIn &&
            typeQualifier.qualifier != EvqPatchOut)
        {
            checkLocationIsNotSpecified(typeQualifier.line, blockLayoutQualifier);
        }
        checkStd430IsForShaderStorageBlock(typeQualifier.line, blockLayoutQualifier.blockStorage,
                                           typeQualifier.qualifier);
    
        if (blockLayoutQualifier.matrixPacking == EmpUnspecified)
        {
            if (typeQualifier.qualifier == EvqUniform)
            {
                blockLayoutQualifier.matrixPacking = mDefaultUniformMatrixPacking;
            }
            else if (typeQualifier.qualifier == EvqBuffer)
            {
                blockLayoutQualifier.matrixPacking = mDefaultBufferMatrixPacking;
            }
        }
    
        if (blockLayoutQualifier.blockStorage == EbsUnspecified)
        {
            if (typeQualifier.qualifier == EvqUniform)
            {
                blockLayoutQualifier.blockStorage = mDefaultUniformBlockStorage;
            }
            else if (typeQualifier.qualifier == EvqBuffer)
            {
                blockLayoutQualifier.blockStorage = mDefaultBufferBlockStorage;
            }
        }
    
        checkWorkGroupSizeIsNotSpecified(nameLine, blockLayoutQualifier);
    
        checkInternalFormatIsNotSpecified(nameLine, blockLayoutQualifier.imageInternalFormat);
    
        // check for sampler types and apply layout qualifiers
        for (size_t memberIndex = 0; memberIndex < fieldList->size(); ++memberIndex)
        {
            TField *field    = (*fieldList)[memberIndex];
            TType *fieldType = field->type();
            if (ContainsOpaque<IsOpaqueFunc>(*fieldType))
            {
                error(field->line(), "Opaque types are not allowed in interface blocks", blockName);
            }
    
            const TQualifier qualifier = fieldType->getQualifier();
            switch (qualifier)
            {
                case EvqGlobal:
                    break;
                case EvqUniform:
                    if (typeQualifier.qualifier == EvqBuffer)
                    {
                        error(field->line(), "invalid qualifier on shader storage block member",
                              getQualifierString(qualifier));
                    }
                    break;
                case EvqBuffer:
                    if (typeQualifier.qualifier == EvqUniform)
                    {
                        error(field->line(), "invalid qualifier on uniform block member",
                              getQualifierString(qualifier));
                    }
                    break;
                // a member variable in io block may have different interpolation.
                case EvqSmoothIn:
                case EvqSmoothOut:
                case EvqFlatIn:
                case EvqFlatOut:
                case EvqNoPerspectiveIn:
                case EvqNoPerspectiveOut:
                case EvqCentroidIn:
                case EvqCentroidOut:
                case EvqSampleIn:
                case EvqSampleOut:
                case EvqNoPerspectiveCentroidIn:
                case EvqNoPerspectiveCentroidOut:
                case EvqNoPerspectiveSampleIn:
                case EvqNoPerspectiveSampleOut:
                    break;
                // a member variable can have an incomplete qualifier because shader io block has either
                // in or out.
                case EvqSmooth:
                case EvqFlat:
                case EvqNoPerspective:
                case EvqCentroid:
                case EvqSample:
                case EvqNoPerspectiveCentroid:
                case EvqNoPerspectiveSample:
                case EvqGeometryIn:
                case EvqGeometryOut:
                    if (!IsShaderIoBlock(typeQualifier.qualifier) &&
                        typeQualifier.qualifier != EvqPatchIn &&
                        typeQualifier.qualifier != EvqPatchOut &&
                        typeQualifier.qualifier != EvqGeometryIn &&
                        typeQualifier.qualifier != EvqGeometryOut)
                    {
                        error(field->line(), "invalid qualifier on interface block member",
                              getQualifierString(qualifier));
                    }
                    break;
                default:
                    error(field->line(), "invalid qualifier on interface block member",
                          getQualifierString(qualifier));
                    break;
            }
    
            // On interface block members, invariant is only applicable to output I/O blocks.
            const bool isOutputShaderIoBlock = isShaderIoBlock && IsShaderOut(typeQualifier.qualifier);
            if (fieldType->isInvariant() && !isOutputShaderIoBlock)
            {
                error(field->line(), "invalid qualifier on interface block member", "invariant");
            }
    
            // check layout qualifiers
            TLayoutQualifier fieldLayoutQualifier = fieldType->getLayoutQualifier();
            checkIndexIsNotSpecified(field->line(), fieldLayoutQualifier.index);
            checkBindingIsNotSpecified(field->line(), fieldLayoutQualifier.binding);
    
            if (fieldLayoutQualifier.blockStorage != EbsUnspecified)
            {
                error(field->line(), "invalid layout qualifier: cannot be used here",
                      getBlockStorageString(fieldLayoutQualifier.blockStorage));
            }
    
            if (fieldLayoutQualifier.matrixPacking == EmpUnspecified)
            {
                fieldLayoutQualifier.matrixPacking = blockLayoutQualifier.matrixPacking;
            }
            else if (!fieldType->isMatrix() && fieldType->getBasicType() != EbtStruct)
            {
                warning(field->line(),
                        "extraneous layout qualifier: only has an effect on matrix types",
                        getMatrixPackingString(fieldLayoutQualifier.matrixPacking));
            }
    
            fieldType->setLayoutQualifier(fieldLayoutQualifier);
    
            if (mShaderVersion < 310 || memberIndex != fieldList->size() - 1u ||
                typeQualifier.qualifier != EvqBuffer)
            {
                // ESSL 3.10 spec section 4.1.9 allows for runtime-sized arrays.
                checkIsNotUnsizedArray(field->line(),
                                       "array members of interface blocks must specify a size",
                                       field->name(), field->type());
            }
    
            if (typeQualifier.qualifier == EvqBuffer)
            {
                // set memory qualifiers
                // GLSL ES 3.10 session 4.9 [Memory Access Qualifiers]. When a block declaration is
                // qualified with a memory qualifier, it is as if all of its members were declared with
                // the same memory qualifier.
                const TMemoryQualifier &blockMemoryQualifier = typeQualifier.memoryQualifier;
                TMemoryQualifier fieldMemoryQualifier        = fieldType->getMemoryQualifier();
                fieldMemoryQualifier.readonly |= blockMemoryQualifier.readonly;
                fieldMemoryQualifier.writeonly |= blockMemoryQualifier.writeonly;
                fieldMemoryQualifier.coherent |= blockMemoryQualifier.coherent;
                fieldMemoryQualifier.restrictQualifier |= blockMemoryQualifier.restrictQualifier;
                fieldMemoryQualifier.volatileQualifier |= blockMemoryQualifier.volatileQualifier;
                // TODO(jiajia.qin@intel.com): Decide whether if readonly and writeonly buffer variable
                // is legal. See bug https://github.com/KhronosGroup/OpenGL-API/issues/7
                fieldType->setMemoryQualifier(fieldMemoryQualifier);
            }
    
            // For per-vertex members, apply the appropriate built-in qualifiers to the members.
            if (isGLPerVertex)
            {
                if (field->name() == "gl_Position")
                {
                    fieldType->setQualifier(EvqPosition);
                }
                if (field->name() == "gl_PointSize")
                {
                    fieldType->setQualifier(EvqPointSize);
                }
                if (field->name() == "gl_ClipDistance")
                {
                    fieldType->setQualifier(EvqClipDistance);
                }
                if (field->name() == "gl_CullDistance")
                {
                    fieldType->setQualifier(EvqCullDistance);
                }
            }
        }
    
        SymbolType instanceSymbolType = SymbolType::UserDefined;
        if (isGLPerVertex)
        {
            instanceSymbolType = SymbolType::BuiltIn;
            typeQualifier.qualifier =
                IsVaryingOut(typeQualifier.qualifier) ? EvqPerVertexOut : EvqPerVertexIn;
        }
        TInterfaceBlock *interfaceBlock = new TInterfaceBlock(&symbolTable, blockName, fieldList,
                                                              blockLayoutQualifier, instanceSymbolType);
        if (!symbolTable.declare(interfaceBlock) && isUniformOrBuffer)
        {
            error(nameLine, "redefinition of an interface block name", blockName);
        }
    
        TType *interfaceBlockType =
            new TType(interfaceBlock, typeQualifier.qualifier, blockLayoutQualifier);
        if (arraySizes)
        {
            interfaceBlockType->makeArrays(*arraySizes);
            checkGeometryShaderInputAndSetArraySize(instanceLine, instanceName, interfaceBlockType);
            checkTessellationShaderUnsizedArraysAndSetSize(instanceLine, instanceName,
                                                           interfaceBlockType);
            checkDeclarationIsValidArraySize(instanceLine, instanceName, interfaceBlockType);
        }
    
        // The instance variable gets created to refer to the interface block type from the AST
        // regardless of if there's an instance name. It's created as an empty symbol if there is no
        // instance name.
        TVariable *instanceVariable =
            new TVariable(&symbolTable, instanceName, interfaceBlockType,
                          instanceName.empty() ? SymbolType::Empty : SymbolType::UserDefined);
    
        if (instanceVariable->symbolType() == SymbolType::Empty)
        {
            // define symbols for the members of the interface block
            for (size_t memberIndex = 0; memberIndex < fieldList->size(); ++memberIndex)
            {
                TField *field    = (*fieldList)[memberIndex];
                TType *fieldType = new TType(*field->type());
    
                // set parent pointer of the field variable
                fieldType->setInterfaceBlockField(interfaceBlock, memberIndex);
    
                fieldType->setQualifier(typeQualifier.qualifier);
    
                SymbolType symbolType = SymbolType::UserDefined;
                if (field->name() == "gl_Position" || field->name() == "gl_PointSize" ||
                    field->name() == "gl_ClipDistance" || field->name() == "gl_CullDistance")
                {
                    // These builtins can be redefined only when used within a redefined gl_PerVertex
                    // block
                    if (interfaceBlock->name() != "gl_PerVertex")
                    {
                        error(field->line(), "redefinition in an invalid interface block",
                              field->name());
                    }
                    symbolType = SymbolType::BuiltIn;
                }
                TVariable *fieldVariable =
                    new TVariable(&symbolTable, field->name(), fieldType, symbolType);
                if (!symbolTable.declare(fieldVariable))
                {
                    error(field->line(), "redefinition of an interface block member name",
                          field->name());
                }
            }
        }
        else
        {
            checkIsNotReserved(instanceLine, instanceName);
    
            // add a symbol for this interface block
            if (!symbolTable.declare(instanceVariable))
            {
                error(instanceLine, "redefinition of an interface block instance name", instanceName);
            }
        }
    
        TIntermSymbol *blockSymbol = new TIntermSymbol(instanceVariable);
        blockSymbol->setLine(typeQualifier.line);
        TIntermDeclaration *declaration = new TIntermDeclaration();
        declaration->appendDeclarator(blockSymbol);
        declaration->setLine(nameLine);
    
        exitStructDeclaration();
        return declaration;
    }
    
    void TParseContext::enterStructDeclaration(const TSourceLoc &line,
                                               const ImmutableString &identifier)
    {
        ++mStructNestingLevel;
    
        // Embedded structure definitions are not supported per GLSL ES spec.
        // ESSL 1.00.17 section 10.9. ESSL 3.00.6 section 12.11.
        if (mStructNestingLevel > 1)
        {
            error(line, "Embedded struct definitions are not allowed", "struct");
        }
    }
    
    void TParseContext::exitStructDeclaration()
    {
        --mStructNestingLevel;
    }
    
    void TParseContext::checkIsBelowStructNestingLimit(const TSourceLoc &line, const TField &field)
    {
        if (!sh::IsWebGLBasedSpec(mShaderSpec))
        {
            return;
        }
    
        if (field.type()->getBasicType() != EbtStruct)
        {
            return;
        }
    
        // We're already inside a structure definition at this point, so add
        // one to the field's struct nesting.
        if (1 + field.type()->getDeepestStructNesting() > kWebGLMaxStructNesting)
        {
            std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
            if (field.type()->getStruct()->symbolType() == SymbolType::Empty)
            {
                // This may happen in case there are nested struct definitions. While they are also
                // invalid GLSL, they don't cause a syntax error.
                reasonStream << "Struct nesting";
            }
            else
            {
                reasonStream << "Reference of struct type " << field.type()->getStruct()->name();
            }
            reasonStream << " exceeds maximum allowed nesting level of " << kWebGLMaxStructNesting;
            std::string reason = reasonStream.str();
            error(line, reason.c_str(), field.name());
            return;
        }
    }
    
    //
    // Parse an array index expression
    //
    TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression,
                                                    const TSourceLoc &location,
                                                    TIntermTyped *indexExpression)
    {
        if (!baseExpression->isArray() && !baseExpression->isMatrix() && !baseExpression->isVector())
        {
            if (baseExpression->getAsSymbolNode())
            {
                error(location, " left of '[' is not of type array, matrix, or vector ",
                      baseExpression->getAsSymbolNode()->getName());
            }
            else
            {
                error(location, " left of '[' is not of type array, matrix, or vector ", "expression");
            }
    
            return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
        }
    
        if (baseExpression->getQualifier() == EvqPerVertexIn)
        {
            if (mGeometryShaderInputPrimitiveType == EptUndefined &&
                mShaderType == GL_GEOMETRY_SHADER_EXT)
            {
                error(location, "missing input primitive declaration before indexing gl_in.", "[");
                return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
            }
        }
    
        TIntermConstantUnion *indexConstantUnion = indexExpression->getAsConstantUnion();
    
        // ES3.2 or ES3.1's EXT_gpu_shader5 allow dynamically uniform expressions to be used as indices
        // of opaque types (samplers and atomic counters) as well as UBOs, but not SSBOs and images.
        bool allowUniformIndices = mShaderVersion >= 320 ||
                                   isExtensionEnabled(TExtension::EXT_gpu_shader5) ||
                                   isExtensionEnabled(TExtension::OES_gpu_shader5);
    
        // ANGLE should be able to fold any constant expressions resulting in an integer - but to be
        // safe we don't treat "EvqConst" that's evaluated according to the spec as being sufficient
        // for constness. Some interpretations of the spec have allowed constant expressions with side
        // effects - like array length() method on a non-constant array.
        if (indexExpression->getQualifier() != EvqConst || indexConstantUnion == nullptr)
        {
            if (baseExpression->isInterfaceBlock())
            {
                switch (baseExpression->getQualifier())
                {
                    case EvqPerVertexIn:
                        break;
                    case EvqUniform:
                        if (!allowUniformIndices)
                        {
                            error(location,
                                  "array indexes for uniform block arrays must be constant integral "
                                  "expressions",
                                  "[");
                        }
                        break;
                    case EvqBuffer:
                        error(location,
                              "array indexes for shader storage block arrays must be constant integral "
                              "expressions",
                              "[");
                        break;
                    default:
                        // It's ok for shader I/O blocks to be dynamically indexed
                        if (!IsShaderIoBlock(baseExpression->getQualifier()) &&
                            baseExpression->getQualifier() != EvqPatchIn &&
                            baseExpression->getQualifier() != EvqPatchOut)
                        {
                            // We can reach here only in error cases.
                            ASSERT(mDiagnostics->numErrors() > 0);
                        }
                        break;
                }
            }
            else if (baseExpression->getQualifier() == EvqFragmentOut ||
                     baseExpression->getQualifier() == EvqFragmentInOut)
            {
                error(location,
                      "array indexes for fragment outputs must be constant integral expressions", "[");
            }
            else if (baseExpression->getQualifier() == EvqLastFragData)
            {
                error(location,
                      "array indexes for gl_LastFragData must be constant integral expressions", "[");
            }
            else if (mShaderSpec == SH_WEBGL2_SPEC && baseExpression->getQualifier() == EvqFragData)
            {
                error(location, "array index for gl_FragData must be constant zero", "[");
            }
            else if (mShaderSpec == SH_WEBGL2_SPEC &&
                     baseExpression->getQualifier() == EvqSecondaryFragDataEXT)
            {
                error(location, "array index for gl_SecondaryFragDataEXT must be constant zero", "[");
            }
            else if (baseExpression->isArray())
            {
                TBasicType elementType = baseExpression->getType().getBasicType();
    
                // Note: In Section 12.30 of the ESSL 3.00 spec on p143-144:
                //
                //   Indexing of arrays of samplers by constant-index-expressions is
                //   supported in GLSL ES 1.00. A constant-index-expression is an
                //   expression formed from constant-expressions and certain loop indices,
                //   defined for a subset of loop constructs. Should this functionality be
                //   included in GLSL ES 3.00?
                //
                //   RESOLUTION: No. Arrays of samplers may only be indexed by constant-
                //   integral-expressions.
                if (IsSampler(elementType) && !allowUniformIndices && mShaderVersion > 100)
                {
                    error(location, "array index for samplers must be constant integral expressions",
                          "[");
                }
                else if (IsImage(elementType))
                {
                    error(location,
                          "array indexes for image arrays must be constant integral expressions", "[");
                }
            }
        }
    
        if (indexConstantUnion)
        {
            // If an out-of-range index is not qualified as constant, the behavior in the spec is
            // undefined. This applies even if ANGLE has been able to constant fold it (ANGLE may
            // constant fold expressions that are not constant expressions). The most compatible way to
            // handle this case is to report a warning instead of an error and force the index to be in
            // the correct range.
            bool outOfRangeIndexIsError = indexExpression->getQualifier() == EvqConst;
            int index                   = 0;
            if (indexConstantUnion->getBasicType() == EbtInt)
            {
                index = indexConstantUnion->getIConst(0);
            }
            else if (indexConstantUnion->getBasicType() == EbtUInt)
            {
                index = static_cast<int>(indexConstantUnion->getUConst(0));
            }
    
            int safeIndex = -1;
    
            if (index < 0)
            {
                outOfRangeError(outOfRangeIndexIsError, location, "index expression is negative", "[]");
                safeIndex = 0;
            }
    
            if (!baseExpression->getType().isUnsizedArray())
            {
                if (baseExpression->isArray())
                {
                    if (baseExpression->getQualifier() == EvqFragData && index > 0)
                    {
                        if (!isExtensionEnabled(TExtension::EXT_draw_buffers))
                        {
                            outOfRangeError(outOfRangeIndexIsError, location,
                                            "array index for gl_FragData must be zero when "
                                            "GL_EXT_draw_buffers is disabled",
                                            "[]");
                            safeIndex = 0;
                        }
                    }
                }
                // Only do generic out-of-range check if similar error hasn't already been reported.
                if (safeIndex < 0)
                {
                    if (baseExpression->isArray())
                    {
                        safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
                                                       baseExpression->getOutermostArraySize(),
                                                       "array index out of range");
                    }
                    else if (baseExpression->isMatrix())
                    {
                        safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
                                                       baseExpression->getType().getCols(),
                                                       "matrix field selection out of range");
                    }
                    else
                    {
                        ASSERT(baseExpression->isVector());
                        safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
                                                       baseExpression->getType().getNominalSize(),
                                                       "vector field selection out of range");
                    }
                }
    
                ASSERT(safeIndex >= 0);
                // Data of constant unions can't be changed, because it may be shared with other
                // constant unions or even builtins, like gl_MaxDrawBuffers. Instead use a new
                // sanitized object.
                if (safeIndex != index || indexConstantUnion->getBasicType() != EbtInt)
                {
                    TConstantUnion *safeConstantUnion = new TConstantUnion();
                    safeConstantUnion->setIConst(safeIndex);
                    indexExpression =
                        new TIntermConstantUnion(safeConstantUnion, TType(indexExpression->getType()));
                }
    
                TIntermBinary *node =
                    new TIntermBinary(EOpIndexDirect, baseExpression, indexExpression);
                node->setLine(location);
                return expressionOrFoldedResult(node);
            }
        }
    
        markStaticReadIfSymbol(indexExpression);
        TIntermBinary *node = new TIntermBinary(EOpIndexIndirect, baseExpression, indexExpression);
        node->setLine(location);
        // Indirect indexing can never be constant folded.
        return node;
    }
    
    int TParseContext::checkIndexLessThan(bool outOfRangeIndexIsError,
                                          const TSourceLoc &location,
                                          int index,
                                          unsigned int arraySize,
                                          const char *reason)
    {
        // A negative index should already have been checked.
        ASSERT(index >= 0);
        if (static_cast<unsigned int>(index) >= arraySize)
        {
            std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
            reasonStream << reason << " '" << index << "'";
            std::string token = reasonStream.str();
            outOfRangeError(outOfRangeIndexIsError, location, reason, "[]");
            return arraySize - 1;
        }
        return index;
    }
    
    TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpression,
                                                             const TSourceLoc &dotLocation,
                                                             const ImmutableString &fieldString,
                                                             const TSourceLoc &fieldLocation)
    {
        if (baseExpression->isArray())
        {
            error(fieldLocation, "cannot apply dot operator to an array", ".");
            return baseExpression;
        }
    
        if (baseExpression->isVector())
        {
            TVector<uint32_t> fieldOffsets;
            if (!parseVectorFields(fieldLocation, fieldString, baseExpression->getNominalSize(),
                                   &fieldOffsets))
            {
                fieldOffsets.resize(1);
                fieldOffsets[0] = 0;
            }
            TIntermSwizzle *node = new TIntermSwizzle(baseExpression, fieldOffsets);
            node->setLine(dotLocation);
    
            return node->fold(mDiagnostics);
        }
        else if (baseExpression->getBasicType() == EbtStruct)
        {
            const TFieldList &fields = baseExpression->getType().getStruct()->fields();
            if (fields.empty())
            {
                error(dotLocation, "structure has no fields", "Internal Error");
                return baseExpression;
            }
            else
            {
                bool fieldFound = false;
                unsigned int i;
                for (i = 0; i < fields.size(); ++i)
                {
                    if (fields[i]->name() == fieldString)
                    {
                        fieldFound = true;
                        break;
                    }
                }
                if (fieldFound)
                {
                    TIntermTyped *index = CreateIndexNode(i);
                    index->setLine(fieldLocation);
                    TIntermBinary *node =
                        new TIntermBinary(EOpIndexDirectStruct, baseExpression, index);
                    node->setLine(dotLocation);
                    return expressionOrFoldedResult(node);
                }
                else
                {
                    error(dotLocation, " no such field in structure", fieldString);
                    return baseExpression;
                }
            }
        }
        else if (baseExpression->isInterfaceBlock())
        {
            const TFieldList &fields = baseExpression->getType().getInterfaceBlock()->fields();
            if (fields.empty())
            {
                error(dotLocation, "interface block has no fields", "Internal Error");
                return baseExpression;
            }
            else
            {
                bool fieldFound = false;
                unsigned int i;
                for (i = 0; i < fields.size(); ++i)
                {
                    if (fields[i]->name() == fieldString)
                    {
                        fieldFound = true;
                        break;
                    }
                }
                if (fieldFound)
                {
                    TIntermTyped *index = CreateIndexNode(i);
                    index->setLine(fieldLocation);
                    TIntermBinary *node =
                        new TIntermBinary(EOpIndexDirectInterfaceBlock, baseExpression, index);
                    node->setLine(dotLocation);
                    // Indexing interface blocks can never be constant folded.
                    return node;
                }
                else
                {
                    error(dotLocation, " no such field in interface block", fieldString);
                    return baseExpression;
                }
            }
        }
        else
        {
            if (mShaderVersion < 300)
            {
                error(dotLocation, " field selection requires structure or vector on left hand side",
                      fieldString);
            }
            else
            {
                error(dotLocation,
                      " field selection requires structure, vector, or interface block on left hand "
                      "side",
                      fieldString);
            }
            return baseExpression;
        }
    }
    
    TLayoutQualifier TParseContext::parseLayoutQualifier(const ImmutableString &qualifierType,
                                                         const TSourceLoc &qualifierTypeLine)
    {
        TLayoutQualifier qualifier = TLayoutQualifier::Create();
    
        if (qualifierType == "shared")
        {
            if (sh::IsWebGLBasedSpec(mShaderSpec))
            {
                error(qualifierTypeLine, "Only std140 layout is allowed in WebGL", "shared");
            }
            qualifier.blockStorage = EbsShared;
        }
        else if (qualifierType == "packed")
        {
            if (sh::IsWebGLBasedSpec(mShaderSpec))
            {
                error(qualifierTypeLine, "Only std140 layout is allowed in WebGL", "packed");
            }
            qualifier.blockStorage = EbsPacked;
        }
        else if (qualifierType == "std430")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.blockStorage = EbsStd430;
        }
        else if (qualifierType == "std140")
        {
            qualifier.blockStorage = EbsStd140;
        }
        else if (qualifierType == "row_major")
        {
            qualifier.matrixPacking = EmpRowMajor;
        }
        else if (qualifierType == "column_major")
        {
            qualifier.matrixPacking = EmpColumnMajor;
        }
        else if (qualifierType == "location")
        {
            error(qualifierTypeLine, "invalid layout qualifier: location requires an argument",
                  qualifierType);
        }
        else if (qualifierType == "yuv" && mShaderType == GL_FRAGMENT_SHADER)
        {
            if (checkCanUseExtension(qualifierTypeLine, TExtension::EXT_YUV_target))
            {
                qualifier.yuv = true;
            }
        }
        else if (qualifierType == "early_fragment_tests")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.earlyFragmentTests = true;
        }
        else if (qualifierType == "rgba32f")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA32F;
        }
        else if (qualifierType == "rgba16f")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA16F;
        }
        else if (qualifierType == "r32f")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            qualifier.imageInternalFormat = EiifR32F;
        }
        else if (qualifierType == "rgba8")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            qualifier.imageInternalFormat = EiifRGBA8;
        }
        else if (qualifierType == "rgba8_snorm")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA8_SNORM;
        }
        else if (qualifierType == "rgba32i")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA32I;
        }
        else if (qualifierType == "rgba16i")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA16I;
        }
        else if (qualifierType == "rgba8i")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            qualifier.imageInternalFormat = EiifRGBA8I;
        }
        else if (qualifierType == "r32i")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifR32I;
        }
        else if (qualifierType == "rgba32ui")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA32UI;
        }
        else if (qualifierType == "rgba16ui")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            qualifier.imageInternalFormat = EiifRGBA16UI;
        }
        else if (qualifierType == "rgba8ui")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            qualifier.imageInternalFormat = EiifRGBA8UI;
        }
        else if (qualifierType == "r32ui")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            qualifier.imageInternalFormat = EiifR32UI;
        }
        else if (mShaderType == GL_GEOMETRY_SHADER_EXT &&
                 (mShaderVersion >= 320 ||
                  (checkCanUseOneOfExtensions(
                       qualifierTypeLine,
                       std::array<TExtension, 2u>{
                           {TExtension::EXT_geometry_shader, TExtension::OES_geometry_shader}}) &&
                   checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310))))
        {
            if (qualifierType == "points")
            {
                qualifier.primitiveType = EptPoints;
            }
            else if (qualifierType == "lines")
            {
                qualifier.primitiveType = EptLines;
            }
            else if (qualifierType == "lines_adjacency")
            {
                qualifier.primitiveType = EptLinesAdjacency;
            }
            else if (qualifierType == "triangles")
            {
                qualifier.primitiveType = EptTriangles;
            }
            else if (qualifierType == "triangles_adjacency")
            {
                qualifier.primitiveType = EptTrianglesAdjacency;
            }
            else if (qualifierType == "line_strip")
            {
                qualifier.primitiveType = EptLineStrip;
            }
            else if (qualifierType == "triangle_strip")
            {
                qualifier.primitiveType = EptTriangleStrip;
            }
            else
            {
                error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
            }
        }
        else if (mShaderType == GL_TESS_EVALUATION_SHADER_EXT &&
                 (mShaderVersion >= 320 ||
                  (checkCanUseOneOfExtensions(
                       qualifierTypeLine,
                       std::array<TExtension, 2u>{{TExtension::EXT_tessellation_shader,
                                                   TExtension::OES_tessellation_shader}}) &&
                   checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310))))
        {
            if (qualifierType == "triangles")
            {
                qualifier.tesPrimitiveType = EtetTriangles;
            }
            else if (qualifierType == "quads")
            {
                qualifier.tesPrimitiveType = EtetQuads;
            }
            else if (qualifierType == "isolines")
            {
                qualifier.tesPrimitiveType = EtetIsolines;
            }
            else if (qualifierType == "equal_spacing")
            {
                qualifier.tesVertexSpacingType = EtetEqualSpacing;
            }
            else if (qualifierType == "fractional_even_spacing")
            {
                qualifier.tesVertexSpacingType = EtetFractionalEvenSpacing;
            }
            else if (qualifierType == "fractional_odd_spacing")
            {
                qualifier.tesVertexSpacingType = EtetFractionalOddSpacing;
            }
            else if (qualifierType == "cw")
            {
                qualifier.tesOrderingType = EtetCw;
            }
            else if (qualifierType == "ccw")
            {
                qualifier.tesOrderingType = EtetCcw;
            }
            else if (qualifierType == "point_mode")
            {
                qualifier.tesPointType = EtetPointMode;
            }
            else
            {
                error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
            }
        }
        else if (mShaderType == GL_FRAGMENT_SHADER)
        {
            if (qualifierType == "noncoherent")
            {
                if (checkCanUseOneOfExtensions(
                        qualifierTypeLine,
                        std::array<TExtension, 2u>{
                            {TExtension::EXT_shader_framebuffer_fetch,
                             TExtension::EXT_shader_framebuffer_fetch_non_coherent}}))
                {
                    checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 100);
                    qualifier.noncoherent = true;
                }
            }
            else if (qualifierType == "blend_support_multiply")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Multiply, &qualifier);
            }
            else if (qualifierType == "blend_support_screen")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Screen, &qualifier);
            }
            else if (qualifierType == "blend_support_overlay")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Overlay, &qualifier);
            }
            else if (qualifierType == "blend_support_darken")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Darken, &qualifier);
            }
            else if (qualifierType == "blend_support_lighten")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Lighten, &qualifier);
            }
            else if (qualifierType == "blend_support_colordodge")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Colordodge, &qualifier);
            }
            else if (qualifierType == "blend_support_colorburn")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Colorburn, &qualifier);
            }
            else if (qualifierType == "blend_support_hardlight")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Hardlight, &qualifier);
            }
            else if (qualifierType == "blend_support_softlight")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Softlight, &qualifier);
            }
            else if (qualifierType == "blend_support_difference")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Difference, &qualifier);
            }
            else if (qualifierType == "blend_support_exclusion")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::Exclusion, &qualifier);
            }
            else if (qualifierType == "blend_support_hsl_hue")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::HslHue, &qualifier);
            }
            else if (qualifierType == "blend_support_hsl_saturation")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::HslSaturation, &qualifier);
            }
            else if (qualifierType == "blend_support_hsl_color")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::HslColor, &qualifier);
            }
            else if (qualifierType == "blend_support_hsl_luminosity")
            {
                AddAdvancedBlendEquation(gl::BlendEquationType::HslLuminosity, &qualifier);
            }
            else if (qualifierType == "blend_support_all_equations")
            {
                qualifier.advancedBlendEquations.setAll();
            }
            else if (qualifierType == "depth_any")
            {
                qualifier.depth = EdAny;
            }
            else if (qualifierType == "depth_greater")
            {
                qualifier.depth = EdGreater;
            }
            else if (qualifierType == "depth_less")
            {
                qualifier.depth = EdLess;
            }
            else if (qualifierType == "depth_unchanged" && !sh::IsWebGLBasedSpec(mShaderSpec))
            {
                qualifier.depth = EdUnchanged;
            }
            else
            {
                error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
            }
    
            if (qualifier.advancedBlendEquations.any() && mShaderVersion < 320)
            {
                if (!checkCanUseExtension(qualifierTypeLine, TExtension::KHR_blend_equation_advanced))
                {
                    qualifier.advancedBlendEquations.reset();
                }
            }
        }
        else
        {
            error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
        }
    
        return qualifier;
    }
    
    void TParseContext::parseLocalSize(const ImmutableString &qualifierType,
                                       const TSourceLoc &qualifierTypeLine,
                                       int intValue,
                                       const TSourceLoc &intValueLine,
                                       const std::string &intValueString,
                                       size_t index,
                                       sh::WorkGroupSize *localSize)
    {
        checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
        if (intValue < 1)
        {
            std::stringstream reasonStream = sh::InitializeStream<std::stringstream>();
            reasonStream << "out of range: " << getWorkGroupSizeString(index) << " must be positive";
            std::string reason = reasonStream.str();
            error(intValueLine, reason.c_str(), intValueString.c_str());
        }
        (*localSize)[index] = intValue;
    }
    
    void TParseContext::parseNumViews(int intValue,
                                      const TSourceLoc &intValueLine,
                                      const std::string &intValueString,
                                      int *numViews)
    {
        // This error is only specified in WebGL, but tightens unspecified behavior in the native
        // specification.
        if (intValue < 1)
        {
            error(intValueLine, "out of range: num_views must be positive", intValueString.c_str());
        }
        *numViews = intValue;
    }
    
    void TParseContext::parseInvocations(int intValue,
                                         const TSourceLoc &intValueLine,
                                         const std::string &intValueString,
                                         int *numInvocations)
    {
        // Although SPEC isn't clear whether invocations can be less than 1, we add this limit because
        // it doesn't make sense to accept invocations <= 0.
        if (intValue < 1 || intValue > mMaxGeometryShaderInvocations)
        {
            error(intValueLine,
                  "out of range: invocations must be in the range of [1, "
                  "MAX_GEOMETRY_SHADER_INVOCATIONS_OES]",
                  intValueString.c_str());
        }
        else
        {
            *numInvocations = intValue;
        }
    }
    
    void TParseContext::parseMaxVertices(int intValue,
                                         const TSourceLoc &intValueLine,
                                         const std::string &intValueString,
                                         int *maxVertices)
    {
        // Although SPEC isn't clear whether max_vertices can be less than 0, we add this limit because
        // it doesn't make sense to accept max_vertices < 0.
        if (intValue < 0 || intValue > mMaxGeometryShaderMaxVertices)
        {
            error(
                intValueLine,
                "out of range: max_vertices must be in the range of [0, gl_MaxGeometryOutputVertices]",
                intValueString.c_str());
        }
        else
        {
            *maxVertices = intValue;
        }
    }
    
    void TParseContext::parseVertices(int intValue,
                                      const TSourceLoc &intValueLine,
                                      const std::string &intValueString,
                                      int *vertices)
    {
        if (intValue < 1 || intValue > mMaxPatchVertices)
        {
            error(intValueLine,
                  "out of range : vertices must be in the range of [1, gl_MaxPatchVertices]",
                  intValueString.c_str());
        }
        else
        {
            *vertices = intValue;
        }
    }
    
    void TParseContext::parseIndexLayoutQualifier(int intValue,
                                                  const TSourceLoc &intValueLine,
                                                  const std::string &intValueString,
                                                  int *index)
    {
        // EXT_blend_func_extended specifies that most validation should happen at link time, but since
        // we're validating output variable locations at compile time, it makes sense to validate that
        // index is 0 or 1 also at compile time. Also since we use "-1" as a placeholder for unspecified
        // index, we can't accept it here.
        if (intValue < 0 || intValue > 1)
        {
            error(intValueLine, "out of range: index layout qualifier can only be 0 or 1",
                  intValueString.c_str());
        }
        else
        {
            *index = intValue;
        }
    }
    
    TLayoutQualifier TParseContext::parseLayoutQualifier(const ImmutableString &qualifierType,
                                                         const TSourceLoc &qualifierTypeLine,
                                                         int intValue,
                                                         const TSourceLoc &intValueLine)
    {
        TLayoutQualifier qualifier = TLayoutQualifier::Create();
    
        std::string intValueString = Str(intValue);
    
        if (qualifierType == "location")
        {
            // must check that location is non-negative
            if (intValue < 0)
            {
                error(intValueLine, "out of range: location must be non-negative",
                      intValueString.c_str());
            }
            else
            {
                qualifier.location           = intValue;
                qualifier.locationsSpecified = 1;
            }
        }
        else if (qualifierType == "binding")
        {
            if (!isExtensionEnabled(TExtension::ANGLE_shader_pixel_local_storage))
            {
                checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            }
            if (intValue < 0)
            {
                error(intValueLine, "out of range: binding must be non-negative",
                      intValueString.c_str());
            }
            else
            {
                qualifier.binding = intValue;
            }
        }
        else if (qualifierType == "offset")
        {
            checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310);
            if (intValue < 0)
            {
                error(intValueLine, "out of range: offset must be non-negative",
                      intValueString.c_str());
            }
            else
            {
                qualifier.offset = intValue;
            }
        }
        else if (qualifierType == "local_size_x")
        {
            parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 0u,
                           &qualifier.localSize);
        }
        else if (qualifierType == "local_size_y")
        {
            parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 1u,
                           &qualifier.localSize);
        }
        else if (qualifierType == "local_size_z")
        {
            parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 2u,
                           &qualifier.localSize);
        }
        else if (qualifierType == "num_views" && mShaderType == GL_VERTEX_SHADER)
        {
            if (checkCanUseOneOfExtensions(
                    qualifierTypeLine, std::array<TExtension, 2u>{
                                           {TExtension::OVR_multiview, TExtension::OVR_multiview2}}))
            {
                parseNumViews(intValue, intValueLine, intValueString, &qualifier.numViews);
            }
        }
        else if (qualifierType == "invocations" && mShaderType == GL_GEOMETRY_SHADER_EXT &&
                 (mShaderVersion >= 320 ||
                  checkCanUseOneOfExtensions(
                      qualifierTypeLine,
                      std::array<TExtension, 2u>{
                          {TExtension::EXT_geometry_shader, TExtension::OES_geometry_shader}})))
        {
            parseInvocations(intValue, intValueLine, intValueString, &qualifier.invocations);
        }
        else if (qualifierType == "max_vertices" && mShaderType == GL_GEOMETRY_SHADER_EXT &&
                 (mShaderVersion >= 320 ||
                  checkCanUseOneOfExtensions(
                      qualifierTypeLine,
                      std::array<TExtension, 2u>{
                          {TExtension::EXT_geometry_shader, TExtension::OES_geometry_shader}})))
        {
            parseMaxVertices(intValue, intValueLine, intValueString, &qualifier.maxVertices);
        }
        else if (qualifierType == "index" && mShaderType == GL_FRAGMENT_SHADER &&
                 checkCanUseExtension(qualifierTypeLine, TExtension::EXT_blend_func_extended))
        {
            parseIndexLayoutQualifier(intValue, intValueLine, intValueString, &qualifier.index);
            if (intValue != 0)
            {
                errorIfPLSDeclared(qualifierTypeLine, PLSIllegalOperations::FragDataIndexNonzero);
            }
        }
        else if (qualifierType == "vertices" && mShaderType == GL_TESS_CONTROL_SHADER_EXT &&
                 (mShaderVersion >= 320 ||
                  checkCanUseOneOfExtensions(
                      qualifierTypeLine,
                      std::array<TExtension, 2u>{
                          {TExtension::EXT_tessellation_shader, TExtension::OES_tessellation_shader}})))
        {
            parseVertices(intValue, intValueLine, intValueString, &qualifier.vertices);
        }
        else
        {
            error(qualifierTypeLine, "invalid layout qualifier", qualifierType);
        }
    
        return qualifier;
    }
    
    TTypeQualifierBuilder *TParseContext::createTypeQualifierBuilder(const TSourceLoc &loc)
    {
        return new TTypeQualifierBuilder(
            new TStorageQualifierWrapper(symbolTable.atGlobalLevel() ? EvqGlobal : EvqTemporary, loc),
            mShaderVersion);
    }
    
    TStorageQualifierWrapper *TParseContext::parseGlobalStorageQualifier(TQualifier qualifier,
                                                                         const TSourceLoc &loc)
    {
        checkIsAtGlobalLevel(loc, getQualifierString(qualifier));
        return new TStorageQualifierWrapper(qualifier, loc);
    }
    
    TStorageQualifierWrapper *TParseContext::parseVaryingQualifier(const TSourceLoc &loc)
    {
        if (getShaderType() == GL_VERTEX_SHADER)
        {
            return parseGlobalStorageQualifier(EvqVaryingOut, loc);
        }
        return parseGlobalStorageQualifier(EvqVaryingIn, loc);
    }
    
    TStorageQualifierWrapper *TParseContext::parseInQualifier(const TSourceLoc &loc)
    {
        if (declaringFunction())
        {
            return new TStorageQualifierWrapper(EvqParamIn, loc);
        }
    
        switch (getShaderType())
        {
            case GL_VERTEX_SHADER:
            {
                if (mShaderVersion < 300 && !anyMultiviewExtensionAvailable())
                {
                    error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "in");
                }
                return new TStorageQualifierWrapper(EvqVertexIn, loc);
            }
            case GL_FRAGMENT_SHADER:
            {
                if (mShaderVersion < 300)
                {
                    error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "in");
                }
                return new TStorageQualifierWrapper(EvqFragmentIn, loc);
            }
            case GL_COMPUTE_SHADER:
            {
                return new TStorageQualifierWrapper(EvqComputeIn, loc);
            }
            case GL_GEOMETRY_SHADER:
            {
                return new TStorageQualifierWrapper(EvqGeometryIn, loc);
            }
            case GL_TESS_CONTROL_SHADER:
            {
                return new TStorageQualifierWrapper(EvqTessControlIn, loc);
            }
            case GL_TESS_EVALUATION_SHADER:
            {
                return new TStorageQualifierWrapper(EvqTessEvaluationIn, loc);
            }
            default:
            {
                UNREACHABLE();
                return new TStorageQualifierWrapper(EvqLast, loc);
            }
        }
    }
    
    TStorageQualifierWrapper *TParseContext::parseOutQualifier(const TSourceLoc &loc)
    {
        if (declaringFunction())
        {
            return new TStorageQualifierWrapper(EvqParamOut, loc);
        }
        switch (getShaderType())
        {
            case GL_VERTEX_SHADER:
            {
                if (mShaderVersion < 300)
                {
                    error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "out");
                }
                return new TStorageQualifierWrapper(EvqVertexOut, loc);
            }
            case GL_FRAGMENT_SHADER:
            {
                if (mShaderVersion < 300)
                {
                    error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "out");
                }
                return new TStorageQualifierWrapper(EvqFragmentOut, loc);
            }
            case GL_COMPUTE_SHADER:
            {
                error(loc, "storage qualifier isn't supported in compute shaders", "out");
                return new TStorageQualifierWrapper(EvqParamOut, loc);
            }
            case GL_GEOMETRY_SHADER_EXT:
            {
                return new TStorageQualifierWrapper(EvqGeometryOut, loc);
            }
            case GL_TESS_CONTROL_SHADER_EXT:
            {
                return new TStorageQualifierWrapper(EvqTessControlOut, loc);
            }
            case GL_TESS_EVALUATION_SHADER_EXT:
            {
                return new TStorageQualifierWrapper(EvqTessEvaluationOut, loc);
            }
            default:
            {
                UNREACHABLE();
                return new TStorageQualifierWrapper(EvqLast, loc);
            }
        }
    }
    
    TStorageQualifierWrapper *TParseContext::parseInOutQualifier(const TSourceLoc &loc)
    {
        if (!declaringFunction())
        {
            if (mShaderVersion < 300)
            {
                error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "inout");
            }
    
            if (getShaderType() != GL_FRAGMENT_SHADER)
            {
                error(loc, "storage qualifier isn't supported in non-fragment shaders", "inout");
            }
    
            if (isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch) ||
                isExtensionEnabled(TExtension::EXT_shader_framebuffer_fetch_non_coherent))
            {
                return new TStorageQualifierWrapper(EvqFragmentInOut, loc);
            }
    
            error(loc,
                  "invalid qualifier: can be used with either function parameters or the variables for "
                  "fetching input attachment data",
                  "inout");
        }
        return new TStorageQualifierWrapper(EvqParamInOut, loc);
    }
    
    TLayoutQualifier TParseContext::joinLayoutQualifiers(TLayoutQualifier leftQualifier,
                                                         TLayoutQualifier rightQualifier,
                                                         const TSourceLoc &rightQualifierLocation)
    {
        return sh::JoinLayoutQualifiers(leftQualifier, rightQualifier, rightQualifierLocation,
                                        mDiagnostics);
    }
    
    TDeclarator *TParseContext::parseStructDeclarator(const ImmutableString &identifier,
                                                      const TSourceLoc &loc)
    {
        return new TDeclarator(identifier, loc);
    }
    
    TDeclarator *TParseContext::parseStructArrayDeclarator(const ImmutableString &identifier,
                                                           const TSourceLoc &loc,
                                                           const TVector<unsigned int> *arraySizes)
    {
        return new TDeclarator(identifier, arraySizes, loc);
    }
    
    void TParseContext::checkDoesNotHaveDuplicateFieldNames(const TFieldList *fields,
                                                            const TSourceLoc &location)
    {
        TUnorderedMap<ImmutableString, uint32_t, ImmutableString::FowlerNollVoHash<sizeof(size_t)>>
            fieldNames;
        for (TField *field : *fields)
        {
            // Note: operator[] adds this name to the map if it doesn't already exist, and initializes
            // its value to 0.
            uint32_t count = ++fieldNames[field->name()];
            if (count != 1)
            {
                error(location, "Duplicate field name in structure", field->name());
            }
        }
    }
    
    void TParseContext::checkDoesNotHaveTooManyFields(const ImmutableString &name,
                                                      const TFieldList *fields,
                                                      const TSourceLoc &location)
    {
        // Check that there are not too many fields.  SPIR-V has a limit of 16383 fields, and it would
        // be reasonable to apply that limit to all outputs.  For example, it was observed that 32768
        // fields cause the Nvidia GL driver to fail compilation, so such a limit is not too specific to
        // SPIR-V.
        constexpr size_t kMaxFieldCount = 16383;
        if (fields->size() > kMaxFieldCount)
        {
            error(location, "Too many fields in the struct (limit is 16383)", name);
        }
    }
    
    TFieldList *TParseContext::addStructFieldList(TFieldList *fields, const TSourceLoc &location)
    {
        return fields;
    }
    
    TFieldList *TParseContext::combineStructFieldLists(TFieldList *processedFields,
                                                       const TFieldList *newlyAddedFields,
                                                       const TSourceLoc &location)
    {
        processedFields->insert(processedFields->end(), newlyAddedFields->begin(),
                                newlyAddedFields->end());
        return processedFields;
    }
    
    TFieldList *TParseContext::addStructDeclaratorListWithQualifiers(
        const TTypeQualifierBuilder &typeQualifierBuilder,
        TPublicType *typeSpecifier,
        const TDeclaratorList *declaratorList)
    {
        TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics);
    
        typeSpecifier->qualifier       = typeQualifier.qualifier;
        typeSpecifier->layoutQualifier = typeQualifier.layoutQualifier;
        typeSpecifier->memoryQualifier = typeQualifier.memoryQualifier;
        typeSpecifier->invariant       = typeQualifier.invariant;
        typeSpecifier->precise         = typeQualifier.precise;
        if (typeQualifier.precision != EbpUndefined)
        {
            typeSpecifier->precision = typeQualifier.precision;
        }
        return addStructDeclaratorList(*typeSpecifier, declaratorList);
    }
    
    TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecifier,
                                                       const TDeclaratorList *declaratorList)
    {
        checkPrecisionSpecified(typeSpecifier.getLine(), typeSpecifier.precision,
                                typeSpecifier.getBasicType());
    
        checkIsNonVoid(typeSpecifier.getLine(), (*declaratorList)[0]->name(),
                       typeSpecifier.getBasicType());
    
        checkWorkGroupSizeIsNotSpecified(typeSpecifier.getLine(), typeSpecifier.layoutQualifier);
        checkEarlyFragmentTestsIsNotSpecified(typeSpecifier.getLine(),
                                              typeSpecifier.layoutQualifier.earlyFragmentTests);
        checkNoncoherentIsNotSpecified(typeSpecifier.getLine(),
                                       typeSpecifier.layoutQualifier.noncoherent);
    
        TFieldList *fieldList = new TFieldList();
    
        for (const TDeclarator *declarator : *declaratorList)
        {
            TType *type = new TType(typeSpecifier);
            if (declarator->isArray())
            {
                // Don't allow arrays of arrays in ESSL < 3.10.
                checkArrayElementIsNotArray(typeSpecifier.getLine(), typeSpecifier);
                type->makeArrays(*declarator->arraySizes());
            }
    
            SymbolType symbolType = SymbolType::UserDefined;
            if (declarator->name() == "gl_Position" || declarator->name() == "gl_PointSize" ||
                declarator->name() == "gl_ClipDistance" || declarator->name() == "gl_CullDistance")
            {
                symbolType = SymbolType::BuiltIn;
            }
            else
            {
                checkIsNotReserved(typeSpecifier.getLine(), declarator->name());
            }
            TField *field = new TField(type, declarator->name(), declarator->line(), symbolType);
            checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *field);
            fieldList->push_back(field);
        }
    
        return fieldList;
    }
    
    TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine,
                                                       const TSourceLoc &nameLine,
                                                       const ImmutableString &structName,
                                                       TFieldList *fieldList)
    {
        SymbolType structSymbolType = SymbolType::UserDefined;
        if (structName.empty())
        {
            structSymbolType = SymbolType::Empty;
        }
    
        // To simplify pulling samplers out of structs, reorder the struct fields to put the samplers at
        // the end.  Structures that *only* contain samplers are also put last.
        TFieldList *reorderedFields = new TFieldList;
        for (TField *field : *fieldList)
        {
            if (!IsSamplerOrStructWithOnlySamplers(field->type()))
            {
                reorderedFields->push_back(field);
            }
        }
        for (TField *field : *fieldList)
        {
            if (IsSamplerOrStructWithOnlySamplers(field->type()))
            {
                reorderedFields->push_back(field);
            }
        }
    
        TStructure *structure =
            new TStructure(&symbolTable, structName, reorderedFields, structSymbolType);
    
        // Store a bool in the struct if we're at global scope, to allow us to
        // skip the local struct scoping workaround in HLSL.
        structure->setAtGlobalScope(symbolTable.atGlobalLevel());
    
        if (structSymbolType != SymbolType::Empty)
        {
            checkIsNotReserved(nameLine, structName);
            if (!symbolTable.declare(structure))
            {
                error(nameLine, "redefinition of a struct", structName);
            }
        }
    
        checkDoesNotHaveTooManyFields(structName, fieldList, structLine);
    
        // Ensure there are no duplicate field names
        checkDoesNotHaveDuplicateFieldNames(fieldList, structLine);
    
        // Ensure we do not specify any storage qualifiers on the struct members
        for (unsigned int typeListIndex = 0; typeListIndex < fieldList->size(); typeListIndex++)
        {
            TField &field              = *(*fieldList)[typeListIndex];
            const TQualifier qualifier = field.type()->getQualifier();
            switch (qualifier)
            {
                case EvqGlobal:
                case EvqTemporary:
                    break;
                default:
                    error(field.line(), "invalid qualifier on struct member",
                          getQualifierString(qualifier));
                    break;
            }
            if (field.type()->isInvariant())
            {
                error(field.line(), "invalid qualifier on struct member", "invariant");
            }
    
            const TLayoutQualifier layoutQualifier = field.type()->getLayoutQualifier();
            if (!layoutQualifier.isEmpty())
            {
                error(field.line(), "invalid layout qualifier on struct member", "layout");
            }
    
            const TMemoryQualifier memoryQualifier = field.type()->getMemoryQualifier();
            if (!memoryQualifier.isEmpty())
            {
                error(field.line(), "invalid memory qualifier on struct member",
                      memoryQualifier.getAnyQualifierString());
            }
    
            if (field.type()->isPrecise())
            {
                error(field.line(), "invalid precise qualifier on struct member", "precise");
            }
    
            // ESSL 3.10 section 4.1.8 -- atomic_uint or images are not allowed as structure member.
            // ANGLE_shader_pixel_local_storage also disallows PLS as struct members.
            if (IsImage(field.type()->getBasicType()) ||
                IsAtomicCounter(field.type()->getBasicType()) ||
                IsPixelLocal(field.type()->getBasicType()))
            {
                error(field.line(), "disallowed type in struct", field.type()->getBasicString());
            }
    
            checkIsNotUnsizedArray(field.line(), "array members of structs must specify a size",
                                   field.name(), field.type());
    
            checkMemoryQualifierIsNotSpecified(field.type()->getMemoryQualifier(), field.line());
    
            checkIndexIsNotSpecified(field.line(), field.type()->getLayoutQualifier().index);
    
            checkBindingIsNotSpecified(field.line(), field.type()->getLayoutQualifier().binding);
    
            checkLocationIsNotSpecified(field.line(), field.type()->getLayoutQualifier());
        }
    
        TTypeSpecifierNonArray typeSpecifierNonArray;
        typeSpecifierNonArray.initializeStruct(structure, true, structLine);
        exitStructDeclaration();
    
        return typeSpecifierNonArray;
    }
    
    TIntermSwitch *TParseContext::addSwitch(TIntermTyped *init,
                                            TIntermBlock *statementList,
                                            const TSourceLoc &loc)
    {
        TBasicType switchType = init->getBasicType();
        if ((switchType != EbtInt && switchType != EbtUInt) || init->isMatrix() || init->isArray() ||
            init->isVector())
        {
            error(init->getLine(), "init-expression in a switch statement must be a scalar integer",
                  "switch");
            return nullptr;
        }
    
        ASSERT(statementList);
        if (!ValidateSwitchStatementList(switchType, mDiagnostics, statementList, loc))
        {
            ASSERT(mDiagnostics->numErrors() > 0);
            return nullptr;
        }
    
        markStaticReadIfSymbol(init);
        TIntermSwitch *node = new TIntermSwitch(init, statementList);
        node->setLine(loc);
        return node;
    }
    
    TIntermCase *TParseContext::addCase(TIntermTyped *condition, const TSourceLoc &loc)
    {
        if (mSwitchNestingLevel == 0)
        {
            error(loc, "case labels need to be inside switch statements", "case");
            return nullptr;
        }
        if (condition == nullptr)
        {
            error(loc, "case label must have a condition", "case");
            return nullptr;
        }
        if ((condition->getBasicType() != EbtInt && condition->getBasicType() != EbtUInt) ||
            condition->isMatrix() || condition->isArray() || condition->isVector())
        {
            error(condition->getLine(), "case label must be a scalar integer", "case");
        }
        TIntermConstantUnion *conditionConst = condition->getAsConstantUnion();
        // ANGLE should be able to fold any EvqConst expressions resulting in an integer - but to be
        // safe against corner cases we still check for conditionConst. Some interpretations of the
        // spec have allowed constant expressions with side effects - like array length() method on a
        // non-constant array.
        if (condition->getQualifier() != EvqConst || conditionConst == nullptr)
        {
            error(condition->getLine(), "case label must be constant", "case");
        }
        TIntermCase *node = new TIntermCase(condition);
        node->setLine(loc);
        return node;
    }
    
    TIntermCase *TParseContext::addDefault(const TSourceLoc &loc)
    {
        if (mSwitchNestingLevel == 0)
        {
            error(loc, "default labels need to be inside switch statements", "default");
            return nullptr;
        }
        TIntermCase *node = new TIntermCase(nullptr);
        node->setLine(loc);
        return node;
    }
    
    TIntermTyped *TParseContext::createUnaryMath(TOperator op,
                                                 TIntermTyped *child,
                                                 const TSourceLoc &loc,
                                                 const TFunction *func)
    {
        ASSERT(child != nullptr);
    
        switch (op)
        {
            case EOpLogicalNot:
                if (child->getBasicType() != EbtBool || child->isMatrix() || child->isArray() ||
                    child->isVector())
                {
                    unaryOpError(loc, GetOperatorString(op), child->getType());
                    return nullptr;
                }
                break;
            case EOpBitwiseNot:
                if ((child->getBasicType() != EbtInt && child->getBasicType() != EbtUInt) ||
                    child->isMatrix() || child->isArray())
                {
                    unaryOpError(loc, GetOperatorString(op), child->getType());
                    return nullptr;
                }
                break;
            case EOpPostIncrement:
            case EOpPreIncrement:
            case EOpPostDecrement:
            case EOpPreDecrement:
            case EOpNegative:
            case EOpPositive:
                if (child->getBasicType() == EbtStruct || child->isInterfaceBlock() ||
                    child->getBasicType() == EbtBool || child->isArray() ||
                    child->getBasicType() == EbtVoid || IsOpaqueType(child->getBasicType()))
                {
                    unaryOpError(loc, GetOperatorString(op), child->getType());
                    return nullptr;
                }
                break;
            // Operators for math built-ins are already type checked against their prototype.
            default:
                break;
        }
    
        if (child->getMemoryQualifier().writeonly)
        {
            const char *opStr =
                BuiltInGroup::IsBuiltIn(op) ? func->name().data() : GetOperatorString(op);
            unaryOpError(loc, opStr, child->getType());
            return nullptr;
        }
    
        markStaticReadIfSymbol(child);
        TIntermUnary *node = new TIntermUnary(op, child, func);
        node->setLine(loc);
    
        return node->fold(mDiagnostics);
    }
    
    TIntermTyped *TParseContext::addUnaryMath(TOperator op, TIntermTyped *child, const TSourceLoc &loc)
    {
        ASSERT(op != EOpNull);
        TIntermTyped *node = createUnaryMath(op, child, loc, nullptr);
        if (node == nullptr)
        {
            return child;
        }
        return node;
    }
    
    TIntermTyped *TParseContext::addUnaryMathLValue(TOperator op,
                                                    TIntermTyped *child,
                                                    const TSourceLoc &loc)
    {
        checkCanBeLValue(loc, GetOperatorString(op), child);
        return addUnaryMath(op, child, loc);
    }
    
    TIntermTyped *TParseContext::expressionOrFoldedResult(TIntermTyped *expression)
    {
        // If we can, we should return the folded version of the expression for subsequent parsing. This
        // enables folding the containing expression during parsing as well, instead of the separate
        // FoldExpressions() step where folding nested expressions requires multiple full AST
        // traversals.
    
        // Even if folding fails the fold() functions return some node representing the expression,
        // typically the original node. So "folded" can be assumed to be non-null.
        TIntermTyped *folded = expression->fold(mDiagnostics);
        ASSERT(folded != nullptr);
        if (folded->getQualifier() == expression->getQualifier())
        {
            // We need this expression to have the correct qualifier when validating the consuming
            // expression. So we can only return the folded node from here in case it has the same
            // qualifier as the original expression. In this kind of a cases the qualifier of the folded
            // node is EvqConst, whereas the qualifier of the expression is EvqTemporary:
            //  1. (true ? 1.0 : non_constant)
            //  2. (non_constant, 1.0)
            return folded;
        }
        return expression;
    }
    
    bool TParseContext::binaryOpCommonCheck(TOperator op,
                                            TIntermTyped *left,
                                            TIntermTyped *right,
                                            const TSourceLoc &loc)
    {
        if (left->getBasicType() == EbtVoid || right->getBasicType() == EbtVoid)
        {
            error(loc, "operation with void operands", GetOperatorString(op));
            return false;
        }
        // Check opaque types are not allowed to be operands in expressions other than array indexing
        // and structure member selection.
        if (IsOpaqueType(left->getBasicType()) || IsOpaqueType(right->getBasicType()))
        {
            switch (op)
            {
                case EOpIndexDirect:
                case EOpIndexIndirect:
                    break;
    
                default:
                    ASSERT(op != EOpIndexDirectStruct);
                    error(loc, "Invalid operation for variables with an opaque type",
                          GetOperatorString(op));
                    return false;
            }
        }
    
        if (right->getMemoryQualifier().writeonly)
        {
            error(loc, "Invalid operation for variables with writeonly", GetOperatorString(op));
            return false;
        }
    
        if (left->getMemoryQualifier().writeonly)
        {
            switch (op)
            {
                case EOpAssign:
                case EOpInitialize:
                case EOpIndexDirect:
                case EOpIndexIndirect:
                case EOpIndexDirectStruct:
                case EOpIndexDirectInterfaceBlock:
                    break;
                default:
                    error(loc, "Invalid operation for variables with writeonly", GetOperatorString(op));
                    return false;
            }
        }
    
        if (left->getType().getStruct() || right->getType().getStruct())
        {
            switch (op)
            {
                case EOpIndexDirectStruct:
                    ASSERT(left->getType().getStruct());
                    break;
                case EOpEqual:
                case EOpNotEqual:
                case EOpAssign:
                case EOpInitialize:
                    if (left->getType() != right->getType())
                    {
                        return false;
                    }
                    break;
                default:
                    error(loc, "Invalid operation for structs", GetOperatorString(op));
                    return false;
            }
        }
    
        if (left->isInterfaceBlock() || right->isInterfaceBlock())
        {
            switch (op)
            {
                case EOpIndexDirectInterfaceBlock:
                    ASSERT(left->getType().getInterfaceBlock());
                    break;
                default:
                    error(loc, "Invalid operation for interface blocks", GetOperatorString(op));
                    return false;
            }
        }
    
        if (left->isArray() != right->isArray())
        {
            error(loc, "array / non-array mismatch", GetOperatorString(op));
            return false;
        }
    
        if (left->isArray())
        {
            ASSERT(right->isArray());
            if (mShaderVersion < 300)
            {
                error(loc, "Invalid operation for arrays", GetOperatorString(op));
                return false;
            }
    
            switch (op)
            {
                case EOpEqual:
                case EOpNotEqual:
                case EOpAssign:
                case EOpInitialize:
                    break;
                default:
                    error(loc, "Invalid operation for arrays", GetOperatorString(op));
                    return false;
            }
            // At this point, size of implicitly sized arrays should be resolved.
            if (left->getType().getArraySizes() != right->getType().getArraySizes())
            {
                error(loc, "array size mismatch", GetOperatorString(op));
                return false;
            }
        }
    
        // Check ops which require integer / ivec parameters
        bool isBitShift = false;
        switch (op)
        {
            case EOpBitShiftLeft:
            case EOpBitShiftRight:
            case EOpBitShiftLeftAssign:
            case EOpBitShiftRightAssign:
                // Unsigned can be bit-shifted by signed and vice versa, but we need to
                // check that the basic type is an integer type.
                isBitShift = true;
                if (!IsInteger(left->getBasicType()) || !IsInteger(right->getBasicType()))
                {
                    return false;
                }
                break;
            case EOpBitwiseAnd:
            case EOpBitwiseXor:
            case EOpBitwiseOr:
            case EOpBitwiseAndAssign:
            case EOpBitwiseXorAssign:
            case EOpBitwiseOrAssign:
                // It is enough to check the type of only one operand, since later it
                // is checked that the operand types match.
                if (!IsInteger(left->getBasicType()))
                {
                    return false;
                }
                break;
            default:
                break;
        }
    
        // Implicit type casting is not allowed in ESSL.
        if (!isBitShift && left->getBasicType() != right->getBasicType())
        {
            return false;
        }
    
        // Check that:
        // 1. Type sizes match exactly on ops that require that.
        // 2. Restrictions for structs that contain arrays or samplers are respected.
        // 3. Arithmetic op type dimensionality restrictions for ops other than multiply are respected.
        switch (op)
        {
            case EOpAssign:
            case EOpInitialize:
            case EOpEqual:
            case EOpNotEqual:
                // ESSL 1.00 sections 5.7, 5.8, 5.9
                if (mShaderVersion < 300 && left->getType().isStructureContainingArrays())
                {
                    error(loc, "undefined operation for structs containing arrays",
                          GetOperatorString(op));
                    return false;
                }
                // Samplers as l-values are disallowed also in ESSL 3.00, see section 4.1.7,
                // we interpret the spec so that this extends to structs containing samplers,
                // similarly to ESSL 1.00 spec.
                if ((mShaderVersion < 300 || op == EOpAssign || op == EOpInitialize) &&
                    left->getType().isStructureContainingSamplers())
                {
                    error(loc, "undefined operation for structs containing samplers",
                          GetOperatorString(op));
                    return false;
                }
    
                if ((left->getNominalSize() != right->getNominalSize()) ||
                    (left->getSecondarySize() != right->getSecondarySize()))
                {
                    error(loc, "dimension mismatch", GetOperatorString(op));
                    return false;
                }
                break;
            case EOpLessThan:
            case EOpGreaterThan:
            case EOpLessThanEqual:
            case EOpGreaterThanEqual:
                if (!left->isScalar() || !right->isScalar())
                {
                    error(loc, "comparison operator only defined for scalars", GetOperatorString(op));
                    return false;
                }
                if (left->getBasicType() == EbtBool || right->getBasicType() == EbtBool)
                {
                    error(loc, "comparison operator not defined for booleans", GetOperatorString(op));
                    return false;
                }
                break;
            case EOpAdd:
            case EOpSub:
            case EOpDiv:
            case EOpIMod:
            case EOpBitShiftLeft:
            case EOpBitShiftRight:
            case EOpBitwiseAnd:
            case EOpBitwiseXor:
            case EOpBitwiseOr:
            case EOpAddAssign:
            case EOpSubAssign:
            case EOpDivAssign:
            case EOpIModAssign:
            case EOpBitShiftLeftAssign:
            case EOpBitShiftRightAssign:
            case EOpBitwiseAndAssign:
            case EOpBitwiseXorAssign:
            case EOpBitwiseOrAssign:
                if ((left->isMatrix() && right->isVector()) || (left->isVector() && right->isMatrix()))
                {
                    return false;
                }
    
                // Are the sizes compatible?
                if (left->getNominalSize() != right->getNominalSize() ||
                    left->getSecondarySize() != right->getSecondarySize())
                {
                    // If the nominal sizes of operands do not match:
                    // One of them must be a scalar.
                    if (!left->isScalar() && !right->isScalar())
                        return false;
    
                    // In the case of compound assignment other than multiply-assign,
                    // the right side needs to be a scalar. Otherwise a vector/matrix
                    // would be assigned to a scalar. A scalar can't be shifted by a
                    // vector either.
                    if (!right->isScalar() &&
                        (IsAssignment(op) || op == EOpBitShiftLeft || op == EOpBitShiftRight))
                        return false;
                }
                break;
            default:
                break;
        }
    
        switch (op)
        {
            case EOpLogicalOr:
            case EOpLogicalXor:
            case EOpLogicalAnd:
                // Above operations are only supported on booleans
                ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() &&
                       !right->getType().getStruct());
                if (left->getBasicType() != EbtBool || !left->isScalar() || !right->isScalar())
                {
                    return false;
                }
                // Basic types matching should have been already checked.
                ASSERT(right->getBasicType() == EbtBool);
                break;
            case EOpAdd:
            case EOpAddAssign:
            case EOpSub:
            case EOpSubAssign:
            case EOpDiv:
            case EOpDivAssign:
            case EOpMul:
            case EOpMulAssign:
                // Above operations are not supported on booleans
                ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() &&
                       !right->getType().getStruct());
                if (left->getBasicType() == EbtBool)
                {
                    return false;
                }
                break;
            case EOpIMod:
            case EOpIModAssign:
                // Mod operator only supported on integers
                // Note that this is only for the % operator, not for mod()
                ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() &&
                       !right->getType().getStruct());
                if (left->getBasicType() != EbtInt && left->getBasicType() != EbtUInt)
                {
                    return false;
                }
                break;
            default:
                break;
        }
    
        return true;
    }
    
    bool TParseContext::isMultiplicationTypeCombinationValid(TOperator op,
                                                             const TType &left,
                                                             const TType &right)
    {
        switch (op)
        {
            case EOpMul:
            case EOpMulAssign:
                return left.getNominalSize() == right.getNominalSize() &&
                       left.getSecondarySize() == right.getSecondarySize();
            case EOpVectorTimesScalar:
                return true;
            case EOpVectorTimesScalarAssign:
                ASSERT(!left.isMatrix() && !right.isMatrix());
                return left.isVector() && !right.isVector();
            case EOpVectorTimesMatrix:
                return left.getNominalSize() == right.getRows();
            case EOpVectorTimesMatrixAssign:
                ASSERT(!left.isMatrix() && right.isMatrix());
                return left.isVector() && left.getNominalSize() == right.getRows() &&
                       left.getNominalSize() == right.getCols();
            case EOpMatrixTimesVector:
                return left.getCols() == right.getNominalSize();
            case EOpMatrixTimesScalar:
                return true;
            case EOpMatrixTimesScalarAssign:
                ASSERT(left.isMatrix() && !right.isMatrix());
                return !right.isVector();
            case EOpMatrixTimesMatrix:
                return left.getCols() == right.getRows();
            case EOpMatrixTimesMatrixAssign:
                ASSERT(left.isMatrix() && right.isMatrix());
                // We need to check two things:
                // 1. The matrix multiplication step is valid.
                // 2. The result will have the same number of columns as the lvalue.
                return left.getCols() == right.getRows() && left.getCols() == right.getCols();
    
            default:
                UNREACHABLE();
                return false;
        }
    }
    
    TIntermTyped *TParseContext::addBinaryMathInternal(TOperator op,
                                                       TIntermTyped *left,
                                                       TIntermTyped *right,
                                                       const TSourceLoc &loc)
    {
        if (!binaryOpCommonCheck(op, left, right, loc))
        {
            return nullptr;
        }
    
        if (op == EOpMul)
        {
            op = TIntermBinary::GetMulOpBasedOnOperands(left->getType(), right->getType());
            if (!isMultiplicationTypeCombinationValid(op, left->getType(), right->getType()))
            {
                return nullptr;
            }
        }
    
        TIntermBinary *node = new TIntermBinary(op, left, right);
        ASSERT(op != EOpAssign);
        markStaticReadIfSymbol(left);
        markStaticReadIfSymbol(right);
        node->setLine(loc);
        return expressionOrFoldedResult(node);
    }
    
    TIntermTyped *TParseContext::addBinaryMath(TOperator op,
                                               TIntermTyped *left,
                                               TIntermTyped *right,
                                               const TSourceLoc &loc)
    {
        TIntermTyped *node = addBinaryMathInternal(op, left, right, loc);
        if (node == nullptr)
        {
            binaryOpError(loc, GetOperatorString(op), left->getType(), right->getType());
            return left;
        }
        return node;
    }
    
    TIntermTyped *TParseContext::addBinaryMathBooleanResult(TOperator op,
                                                            TIntermTyped *left,
                                                            TIntermTyped *right,
                                                            const TSourceLoc &loc)
    {
        TIntermTyped *node = addBinaryMathInternal(op, left, right, loc);
        if (node == nullptr)
        {
            binaryOpError(loc, GetOperatorString(op), left->getType(), right->getType());
            node = CreateBoolNode(false);
            node->setLine(loc);
        }
        return node;
    }
    
    TIntermTyped *TParseContext::addAssign(TOperator op,
                                           TIntermTyped *left,
                                           TIntermTyped *right,
                                           const TSourceLoc &loc)
    {
        checkCanBeLValue(loc, "assign", left);
        TIntermBinary *node = nullptr;
        if (binaryOpCommonCheck(op, left, right, loc))
        {
            TIntermBinary *lValue = left->getAsBinaryNode();
            if ((lValue != nullptr) &&
                (lValue->getOp() == EOpIndexIndirect || lValue->getOp() == EOpIndexDirect) &&
                IsTessellationControlShaderOutput(mShaderType, lValue->getLeft()->getQualifier()))
            {
                checkTCSOutVarIndexIsValid(lValue, loc);
            }
    
            if (op == EOpMulAssign)
            {
                op = TIntermBinary::GetMulAssignOpBasedOnOperands(left->getType(), right->getType());
                if (isMultiplicationTypeCombinationValid(op, left->getType(), right->getType()))
                {
                    node = new TIntermBinary(op, left, right);
                }
            }
            else
            {
                node = new TIntermBinary(op, left, right);
            }
        }
        if (node == nullptr)
        {
            assignError(loc, "assign", left->getType(), right->getType());
            return left;
        }
        if (op != EOpAssign)
        {
            markStaticReadIfSymbol(left);
        }
        markStaticReadIfSymbol(right);
        node->setLine(loc);
        return node;
    }
    
    TIntermTyped *TParseContext::addComma(TIntermTyped *left,
                                          TIntermTyped *right,
                                          const TSourceLoc &loc)
    {
        // WebGL2 section 5.26, the following results in an error:
        // "Sequence operator applied to void, arrays, or structs containing arrays"
        if (mShaderSpec == SH_WEBGL2_SPEC &&
            (left->isArray() || left->getBasicType() == EbtVoid ||
             left->getType().isStructureContainingArrays() || right->isArray() ||
             right->getBasicType() == EbtVoid || right->getType().isStructureContainingArrays()))
        {
            error(loc,
                  "sequence operator is not allowed for void, arrays, or structs containing arrays",
                  ",");
        }
        if (left->isInterfaceBlock() || right->isInterfaceBlock())
        {
            error(loc, "sequence operator is not allowed for interface blocks", ",");
        }
    
        TIntermBinary *commaNode = TIntermBinary::CreateComma(left, right, mShaderVersion);
        markStaticReadIfSymbol(left);
        markStaticReadIfSymbol(right);
        commaNode->setLine(loc);
    
        return expressionOrFoldedResult(commaNode);
    }
    
    TIntermBranch *TParseContext::addBranch(TOperator op, const TSourceLoc &loc)
    {
        switch (op)
        {
            case EOpContinue:
                if (mLoopNestingLevel <= 0)
                {
                    error(loc, "continue statement only allowed in loops", "");
                }
                break;
            case EOpBreak:
                if (mLoopNestingLevel <= 0 && mSwitchNestingLevel <= 0)
                {
                    error(loc, "break statement only allowed in loops and switch statements", "");
                }
                break;
            case EOpReturn:
                if (mCurrentFunctionType->getBasicType() != EbtVoid)
                {
                    error(loc, "non-void function must return a value", "return");
                }
                if (mDeclaringMain)
                {
                    errorIfPLSDeclared(loc, PLSIllegalOperations::ReturnFromMain);
                }
                break;
            case EOpKill:
                if (mShaderType != GL_FRAGMENT_SHADER)
                {
                    error(loc, "discard supported in fragment shaders only", "discard");
                }
                else
                {
                    errorIfPLSDeclared(loc, PLSIllegalOperations::Discard);
                }
                mHasDiscard = true;
                break;
            default:
                UNREACHABLE();
                break;
        }
        return addBranch(op, nullptr, loc);
    }
    
    TIntermBranch *TParseContext::addBranch(TOperator op,
                                            TIntermTyped *expression,
                                            const TSourceLoc &loc)
    {
        if (expression != nullptr)
        {
            markStaticReadIfSymbol(expression);
            ASSERT(op == EOpReturn);
            mFunctionReturnsValue = true;
            if (mCurrentFunctionType->getBasicType() == EbtVoid)
            {
                error(loc, "void function cannot return a value", "return");
            }
            else if (*mCurrentFunctionType != expression->getType())
            {
                error(loc, "function return is not matching type:", "return");
            }
        }
        TIntermBranch *node = new TIntermBranch(op, expression);
        node->setLine(loc);
        return node;
    }
    
    void TParseContext::appendStatement(TIntermBlock *block, TIntermNode *statement)
    {
        if (statement != nullptr)
        {
            markStaticReadIfSymbol(statement);
            block->appendStatement(statement);
        }
    }
    
    void TParseContext::checkTextureGather(TIntermAggregate *functionCall)
    {
        const TOperator op    = functionCall->getOp();
        const TFunction *func = functionCall->getFunction();
        if (BuiltInGroup::IsTextureGather(op))
        {
            bool isTextureGatherOffsetOrOffsets =
                BuiltInGroup::IsTextureGatherOffset(op) || BuiltInGroup::IsTextureGatherOffsets(op);
            TIntermNode *componentNode = nullptr;
            TIntermSequence *arguments = functionCall->getSequence();
            ASSERT(arguments->size() >= 2u && arguments->size() <= 4u);
            const TIntermTyped *sampler = arguments->front()->getAsTyped();
            ASSERT(sampler != nullptr);
            switch (sampler->getBasicType())
            {
                case EbtSampler2D:
                case EbtISampler2D:
                case EbtUSampler2D:
                case EbtSampler2DArray:
                case EbtISampler2DArray:
                case EbtUSampler2DArray:
                    if ((!isTextureGatherOffsetOrOffsets && arguments->size() == 3u) ||
                        (isTextureGatherOffsetOrOffsets && arguments->size() == 4u))
                    {
                        componentNode = arguments->back();
                    }
                    break;
                case EbtSamplerCube:
                case EbtISamplerCube:
                case EbtUSamplerCube:
                case EbtSamplerCubeArray:
                case EbtISamplerCubeArray:
                case EbtUSamplerCubeArray:
                    ASSERT(!isTextureGatherOffsetOrOffsets);
                    if (arguments->size() == 3u)
                    {
                        componentNode = arguments->back();
                    }
                    break;
                case EbtSampler2DShadow:
                case EbtSampler2DArrayShadow:
                case EbtSamplerCubeShadow:
                case EbtSamplerCubeArrayShadow:
                    break;
                default:
                    UNREACHABLE();
                    break;
            }
            if (componentNode)
            {
                const TIntermConstantUnion *componentConstantUnion =
                    componentNode->getAsConstantUnion();
                if (componentNode->getAsTyped()->getQualifier() != EvqConst || !componentConstantUnion)
                {
                    error(functionCall->getLine(), "Texture component must be a constant expression",
                          func->name());
                }
                else
                {
                    int component = componentConstantUnion->getIConst(0);
                    if (component < 0 || component > 3)
                    {
                        error(functionCall->getLine(), "Component must be in the range [0;3]",
                              func->name());
                    }
                }
            }
        }
    }
    
    void TParseContext::checkTextureOffset(TIntermAggregate *functionCall)
    {
        const TOperator op         = functionCall->getOp();
        const TFunction *func      = functionCall->getFunction();
        TIntermNode *offset        = nullptr;
        TIntermSequence *arguments = functionCall->getSequence();
    
        if (BuiltInGroup::IsTextureOffsetNoBias(op) || BuiltInGroup::IsTextureGatherOffsetNoComp(op) ||
            BuiltInGroup::IsTextureGatherOffsetRef(op) ||
            BuiltInGroup::IsTextureGatherOffsetsNoComp(op) ||
            BuiltInGroup::IsTextureGatherOffsetsRef(op))
        {
            offset = arguments->back();
        }
        else if (BuiltInGroup::IsTextureOffsetBias(op) || BuiltInGroup::IsTextureGatherOffsetComp(op) ||
                 BuiltInGroup::IsTextureGatherOffsetsComp(op))
        {
            // A bias or comp parameter follows the offset parameter.
            ASSERT(arguments->size() >= 3);
            offset = (*arguments)[2];
        }
    
        // If not one of the above built-ins, there's nothing to do here.
        if (offset == nullptr)
        {
            return;
        }
    
        bool isTextureGatherOffset             = BuiltInGroup::IsTextureGatherOffset(op);
        bool isTextureGatherOffsets            = BuiltInGroup::IsTextureGatherOffsets(op);
        bool useTextureGatherOffsetConstraints = isTextureGatherOffset || isTextureGatherOffsets;
    
        int minOffsetValue =
            useTextureGatherOffsetConstraints ? mMinProgramTextureGatherOffset : mMinProgramTexelOffset;
        int maxOffsetValue =
            useTextureGatherOffsetConstraints ? mMaxProgramTextureGatherOffset : mMaxProgramTexelOffset;
    
        if (isTextureGatherOffsets)
        {
            // If textureGatherOffsets, the offsets parameter is an array, which is expected as an
            // aggregate constructor node or as a symbol node with a constant value.
            TIntermAggregate *offsetAggregate = offset->getAsAggregate();
            TIntermSymbol *offsetSymbol       = offset->getAsSymbolNode();
    
            const TConstantUnion *offsetValues = offsetAggregate ? offsetAggregate->getConstantValue()
                                                 : offsetSymbol  ? offsetSymbol->getConstantValue()
                                                                 : nullptr;
    
            if (offsetValues == nullptr)
            {
                error(functionCall->getLine(), "Texture offsets must be a constant expression",
                      func->name());
                return;
            }
    
            constexpr unsigned int kOffsetsCount = 4;
            const TType &offsetType =
                offsetAggregate != nullptr ? offsetAggregate->getType() : offsetSymbol->getType();
            if (offsetType.getNumArraySizes() != 1 || offsetType.getArraySizes()[0] != kOffsetsCount)
            {
                error(functionCall->getLine(), "Texture offsets must be an array of 4 elements",
                      func->name());
                return;
            }
    
            size_t size = offsetType.getObjectSize() / kOffsetsCount;
            for (unsigned int i = 0; i < kOffsetsCount; ++i)
            {
                checkSingleTextureOffset(offset->getLine(), &offsetValues[i * size], size,
                                         minOffsetValue, maxOffsetValue);
            }
        }
        else
        {
            // If textureOffset or textureGatherOffset, the offset is expected to be found as a constant
            // union.
            TIntermConstantUnion *offsetConstantUnion = offset->getAsConstantUnion();
    
            // ES3.2 or ES3.1's EXT_gpu_shader5 allow non-const offsets to be passed to
            // textureGatherOffset.
            bool textureGatherOffsetMustBeConst = mShaderVersion <= 310 &&
                                                  !isExtensionEnabled(TExtension::EXT_gpu_shader5) &&
                                                  !isExtensionEnabled(TExtension::OES_gpu_shader5);
    
            bool isOffsetConst =
                offset->getAsTyped()->getQualifier() == EvqConst && offsetConstantUnion != nullptr;
            bool offsetMustBeConst = !isTextureGatherOffset || textureGatherOffsetMustBeConst;
    
            if (!isOffsetConst && offsetMustBeConst)
            {
                error(functionCall->getLine(), "Texture offset must be a constant expression",
                      func->name());
                return;
            }
    
            // We cannot verify non-constant offsets to textureGatherOffset.
            if (offsetConstantUnion == nullptr)
            {
                ASSERT(!offsetMustBeConst);
                return;
            }
    
            size_t size                  = offsetConstantUnion->getType().getObjectSize();
            const TConstantUnion *values = offsetConstantUnion->getConstantValue();
            checkSingleTextureOffset(offset->getLine(), values, size, minOffsetValue, maxOffsetValue);
        }
    }
    
    void TParseContext::checkSingleTextureOffset(const TSourceLoc &line,
                                                 const TConstantUnion *values,
                                                 size_t size,
                                                 int minOffsetValue,
                                                 int maxOffsetValue)
    {
        for (size_t i = 0u; i < size; ++i)
        {
            ASSERT(values[i].getType() == EbtInt);
            int offsetValue = values[i].getIConst();
            if (offsetValue > maxOffsetValue || offsetValue < minOffsetValue)
            {
                std::stringstream tokenStream = sh::InitializeStream<std::stringstream>();
                tokenStream << offsetValue;
                std::string token = tokenStream.str();
                error(line, "Texture offset value out of valid range", token.c_str());
            }
        }
    }
    
    void TParseContext::checkInterpolationFS(TIntermAggregate *functionCall)
    {
        const TFunction *func = functionCall->getFunction();
        if (!BuiltInGroup::IsInterpolationFS(functionCall->getOp()))
        {
            return;
        }
    
        TIntermTyped *arg0 = nullptr;
    
        if (functionCall->getAsAggregate())
        {
            const TIntermSequence *argp = functionCall->getSequence();
            if (argp->size() > 0)
                arg0 = (*argp)[0]->getAsTyped();
        }
        else
        {
            assert(functionCall->getAsUnaryNode());
            arg0 = functionCall->getAsUnaryNode()->getOperand();
        }
    
        // Make sure the first argument is an interpolant, or an array element of an interpolant
        if (!IsVaryingIn(arg0->getType().getQualifier()))
        {
            // It might still be an array element.
            const TIntermTyped *base = FindLValueBase(arg0);
    
            if (base == nullptr || (!IsVaryingIn(base->getType().getQualifier())))
                error(arg0->getLine(),
                      "first argument must be an interpolant, or interpolant-array element",
                      func->name());
        }
    }
    
    void TParseContext::checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall)
    {
        const TFunction *func = functionCall->getFunction();
        if (BuiltInGroup::IsAtomicMemory(functionCall->getOp()))
        {
            TIntermSequence *arguments = functionCall->getSequence();
            TIntermTyped *memNode      = (*arguments)[0]->getAsTyped();
    
            if (IsBufferOrSharedVariable(memNode))
            {
                return;
            }
    
            while (memNode->getAsBinaryNode() || memNode->getAsSwizzleNode())
            {
                // Child 0 is "left" if binary, and the expression being swizzled if swizzle.
                // Note: we don't need to check that the binary operation is one of EOp*Index*, as any
                // other operation will result in a temp value which cannot be passed to this
                // out/inout parameter anyway.
                memNode = memNode->getChildNode(0)->getAsTyped();
                if (IsBufferOrSharedVariable(memNode))
                {
                    return;
                }
            }
    
            error(memNode->getLine(),
                  "The value passed to the mem argument of an atomic memory function does not "
                  "correspond to a buffer or shared variable.",
                  func->name());
        }
    }
    
    // GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers
    void TParseContext::checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall)
    {
        const TOperator op = functionCall->getOp();
    
        if (BuiltInGroup::IsImage(op))
        {
            TIntermSequence *arguments = functionCall->getSequence();
            TIntermTyped *imageNode    = (*arguments)[0]->getAsTyped();
    
            const TMemoryQualifier &memoryQualifier = imageNode->getMemoryQualifier();
    
            if (BuiltInGroup::IsImageStore(op))
            {
                if (memoryQualifier.readonly)
                {
                    error(imageNode->getLine(),
                          "'imageStore' cannot be used with images qualified as 'readonly'",
                          GetImageArgumentToken(imageNode));
                }
            }
            else if (BuiltInGroup::IsImageLoad(op))
            {
                if (memoryQualifier.writeonly)
                {
                    error(imageNode->getLine(),
                          "'imageLoad' cannot be used with images qualified as 'writeonly'",
                          GetImageArgumentToken(imageNode));
                }
            }
            else if (BuiltInGroup::IsImageAtomic(op))
            {
                if (memoryQualifier.readonly)
                {
                    error(imageNode->getLine(),
                          "'imageAtomic' cannot be used with images qualified as 'readonly'",
                          GetImageArgumentToken(imageNode));
                }
                if (memoryQualifier.writeonly)
                {
                    error(imageNode->getLine(),
                          "'imageAtomic' cannot be used with images qualified as 'writeonly'",
                          GetImageArgumentToken(imageNode));
                }
            }
        }
    }
    
    // GLSL ES 3.10 Revision 4, 13.51 Matching of Memory Qualifiers in Function Parameters
    void TParseContext::checkImageMemoryAccessForUserDefinedFunctions(
        const TFunction *functionDefinition,
        const TIntermAggregate *functionCall)
    {
        ASSERT(functionCall->getOp() == EOpCallFunctionInAST);
    
        const TIntermSequence &arguments = *functionCall->getSequence();
    
        ASSERT(functionDefinition->getParamCount() == arguments.size());
    
        for (size_t i = 0; i < arguments.size(); ++i)
        {
            TIntermTyped *typedArgument        = arguments[i]->getAsTyped();
            const TType &functionArgumentType  = typedArgument->getType();
            const TType &functionParameterType = functionDefinition->getParam(i)->getType();
            ASSERT(functionArgumentType.getBasicType() == functionParameterType.getBasicType());
    
            if (IsImage(functionArgumentType.getBasicType()))
            {
                const TMemoryQualifier &functionArgumentMemoryQualifier =
                    functionArgumentType.getMemoryQualifier();
                const TMemoryQualifier &functionParameterMemoryQualifier =
                    functionParameterType.getMemoryQualifier();
                if (functionArgumentMemoryQualifier.readonly &&
                    !functionParameterMemoryQualifier.readonly)
                {
                    error(functionCall->getLine(),
                          "Function call discards the 'readonly' qualifier from image",
                          GetImageArgumentToken(typedArgument));
                }
    
                if (functionArgumentMemoryQualifier.writeonly &&
                    !functionParameterMemoryQualifier.writeonly)
                {
                    error(functionCall->getLine(),
                          "Function call discards the 'writeonly' qualifier from image",
                          GetImageArgumentToken(typedArgument));
                }
    
                if (functionArgumentMemoryQualifier.coherent &&
                    !functionParameterMemoryQualifier.coherent)
                {
                    error(functionCall->getLine(),
                          "Function call discards the 'coherent' qualifier from image",
                          GetImageArgumentToken(typedArgument));
                }
    
                if (functionArgumentMemoryQualifier.volatileQualifier &&
                    !functionParameterMemoryQualifier.volatileQualifier)
                {
                    error(functionCall->getLine(),
                          "Function call discards the 'volatile' qualifier from image",
                          GetImageArgumentToken(typedArgument));
                }
            }
        }
    }
    
    TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunctionLookup *fnCall, const TSourceLoc &loc)
    {
        if (fnCall->thisNode() != nullptr)
        {
            return addMethod(fnCall, loc);
        }
        if (fnCall->isConstructor())
        {
            return addConstructor(fnCall, loc);
        }
        return addNonConstructorFunctionCall(fnCall, loc);
    }
    
    TIntermTyped *TParseContext::addMethod(TFunctionLookup *fnCall, const TSourceLoc &loc)
    {
        TIntermTyped *thisNode = fnCall->thisNode();
        // It's possible for the name pointer in the TFunction to be null in case it gets parsed as
        // a constructor. But such a TFunction can't reach here, since the lexer goes into FIELDS
        // mode after a dot, which makes type identifiers to be parsed as FIELD_SELECTION instead.
        // So accessing fnCall->name() below is safe.
        if (fnCall->name() != "length")
        {
            error(loc, "invalid method", fnCall->name());
        }
        else if (!fnCall->arguments().empty())
        {
            error(loc, "method takes no parameters", "length");
        }
        else if (!thisNode->isArray())
        {
            error(loc, "length can only be called on arrays", "length");
        }
        else if (thisNode->getQualifier() == EvqPerVertexIn &&
                 mGeometryShaderInputPrimitiveType == EptUndefined)
        {
            ASSERT(mShaderType == GL_GEOMETRY_SHADER_EXT);
            error(loc, "missing input primitive declaration before calling length on gl_in", "length");
        }
        else
        {
            TIntermUnary *node = new TIntermUnary(EOpArrayLength, thisNode, nullptr);
            markStaticReadIfSymbol(thisNode);
            node->setLine(loc);
            return node->fold(mDiagnostics);
        }
        return CreateZeroNode(TType(EbtInt, EbpUndefined, EvqConst));
    }
    
    TIntermTyped *TParseContext::addNonConstructorFunctionCallImpl(TFunctionLookup *fnCall,
                                                                   const TSourceLoc &loc)
    {
        // First check whether the function has been hidden by a variable name or struct typename by
        // using the symbol looked up in the lexical phase. If the function is not hidden, look for one
        // with a matching argument list.
        if (fnCall->symbol() != nullptr && !fnCall->symbol()->isFunction())
        {
            error(loc, "function name expected", fnCall->name());
        }
        else
        {
            // There are no inner functions, so it's enough to look for user-defined functions in the
            // global scope.
            const TSymbol *symbol = symbolTable.findGlobal(fnCall->getMangledName());
    
            if (symbol != nullptr)
            {
                // A user-defined function - could be an overloaded built-in as well.
                ASSERT(symbol->symbolType() == SymbolType::UserDefined);
                const TFunction *fnCandidate = static_cast<const TFunction *>(symbol);
                TIntermAggregate *callNode =
                    TIntermAggregate::CreateFunctionCall(*fnCandidate, &fnCall->arguments());
                callNode->setLine(loc);
                checkImageMemoryAccessForUserDefinedFunctions(fnCandidate, callNode);
                functionCallRValueLValueErrorCheck(fnCandidate, callNode);
                return callNode;
            }
    
            symbol = symbolTable.findBuiltIn(fnCall->getMangledName(), mShaderVersion);
    
            if (symbol != nullptr)
            {
                // A built-in function.
                ASSERT(symbol->symbolType() == SymbolType::BuiltIn);
                const TFunction *fnCandidate = static_cast<const TFunction *>(symbol);
    
                if (!fnCandidate->extensions().empty() &&
                    fnCandidate->extensions()[0] != TExtension::UNDEFINED)
                {
                    checkCanUseOneOfExtensions(loc, fnCandidate->extensions());
                }
    
                // All function calls are mapped to a built-in operation.
                TOperator op = fnCandidate->getBuiltInOp();
                if (BuiltInGroup::IsMath(op) && fnCandidate->getParamCount() == 1)
                {
                    // Treat it like a built-in unary operator.
                    TIntermNode *unaryParamNode = fnCall->arguments().front();
                    return createUnaryMath(op, unaryParamNode->getAsTyped(), loc, fnCandidate);
                }
    
                TIntermAggregate *callNode =
                    TIntermAggregate::CreateBuiltInFunctionCall(*fnCandidate, &fnCall->arguments());
                callNode->setLine(loc);
    
                if (UsesDerivatives(callNode))
                {
                    mUsesDerivatives = true;
                }
    
                checkAtomicMemoryBuiltinFunctions(callNode);
                checkTextureOffset(callNode);
                checkTextureGather(callNode);
                checkInterpolationFS(callNode);
                checkImageMemoryAccessForBuiltinFunctions(callNode);
    
                // Some built-in functions have out parameters too.
                functionCallRValueLValueErrorCheck(fnCandidate, callNode);
    
                // See if we can constant fold a built-in. Note that this may be possible
                // even if it is not const-qualified.
                return callNode->fold(mDiagnostics);
            }
            else
            {
                error(loc, "no matching overloaded function found", fnCall->name());
            }
        }
        return nullptr;
    }
    
    TIntermTyped *TParseContext::addNonConstructorFunctionCall(TFunctionLookup *fnCall,
                                                               const TSourceLoc &loc)
    {
        TIntermTyped *result = addNonConstructorFunctionCallImpl(fnCall, loc);
        if (result != nullptr)
        {
            return result;
        }
        // Error message was already written. Put on an unused node for error recovery.
        return CreateZeroNode(TType(EbtFloat, EbpMedium, EvqConst));
    }
    
    TIntermTyped *TParseContext::addTernarySelection(TIntermTyped *cond,
                                                     TIntermTyped *trueExpression,
                                                     TIntermTyped *falseExpression,
                                                     const TSourceLoc &loc)
    {
        if (!checkIsScalarBool(loc, cond))
        {
            return falseExpression;
        }
    
        if (trueExpression->getType() != falseExpression->getType())
        {
            TInfoSinkBase reasonStream;
            reasonStream << "mismatching ternary operator operand types '" << trueExpression->getType()
                         << " and '" << falseExpression->getType() << "'";
            error(loc, reasonStream.c_str(), "?:");
            return falseExpression;
        }
        if (IsOpaqueType(trueExpression->getBasicType()))
        {
            // ESSL 1.00 section 4.1.7
            // ESSL 3.00.6 section 4.1.7
            // Opaque/sampler types are not allowed in most types of expressions, including ternary.
            // Note that structs containing opaque types don't need to be checked as structs are
            // forbidden below.
            error(loc, "ternary operator is not allowed for opaque types", "?:");
            return falseExpression;
        }
    
        if (cond->getMemoryQualifier().writeonly || trueExpression->getMemoryQualifier().writeonly ||
            falseExpression->getMemoryQualifier().writeonly)
        {
            error(loc, "ternary operator is not allowed for variables with writeonly", "?:");
            return falseExpression;
        }
    
        // ESSL 1.00.17 sections 5.2 and 5.7:
        // Ternary operator is not among the operators allowed for structures/arrays.
        // ESSL 3.00 and ESSL 3.10 section 5.7:
        // Ternary operator supports structs, but array support is optional for arrays.
        // ESSL 3.20 section 5.7:
        // Ternary operator supports structs and arrays unconditionally.
        // In WebGL2 section 5.26, ternary is banned for both arrays and structs.
        if ((mShaderVersion < 300 || mShaderSpec == SH_WEBGL2_SPEC) && trueExpression->isArray())
        {
            error(loc, "ternary operator is not allowed for arrays in ESSL 1.0 and webgl", "?:");
            return falseExpression;
        }
        if ((mShaderVersion < 300 || mShaderSpec == SH_WEBGL2_SPEC) &&
            trueExpression->getBasicType() == EbtStruct)
        {
            error(loc, "ternary operator is not allowed for structures in ESSL 1.0 and webgl", "?:");
            return falseExpression;
        }
        if (trueExpression->getBasicType() == EbtInterfaceBlock)
        {
            error(loc, "ternary operator is not allowed for interface blocks", "?:");
            return falseExpression;
        }
    
        // WebGL2 section 5.26, the following results in an error:
        // "Ternary operator applied to void, arrays, or structs containing arrays"
        if (mShaderSpec == SH_WEBGL2_SPEC && trueExpression->getBasicType() == EbtVoid)
        {
            error(loc, "ternary operator is not allowed for void", "?:");
            return falseExpression;
        }
    
        TIntermTernary *node = new TIntermTernary(cond, trueExpression, falseExpression);
        markStaticReadIfSymbol(cond);
        markStaticReadIfSymbol(trueExpression);
        markStaticReadIfSymbol(falseExpression);
        node->setLine(loc);
        return expressionOrFoldedResult(node);
    }
    
    //
    // Parse an array of strings using yyparse.
    //
    // Returns 0 for success.
    //
    int PaParseStrings(size_t count,
                       const char *const string[],
                       const int length[],
                       TParseContext *context)
    {
        if ((count == 0) || (string == nullptr))
            return 1;
    
        if (glslang_initialize(context))
            return 1;
    
        int error = glslang_scan(count, string, length, context);
        if (!error)
            error = glslang_parse(context);
    
        glslang_finalize(context);
    
        return (error == 0) && (context->numErrors() == 0) ? 0 : 1;
    }
    
    }  // namespace sh