Raw File
check.cpp
#include "syntax-visitors.h"

#include "lookup.h"
#include "compiler.h"
#include "visitor.h"

#include "../core/secure-crt.h"
#include <assert.h>

namespace Slang
{

        /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration?
    bool isEffectivelyStatic(
        Decl*           decl,
        ContainerDecl*  parentDecl)
    {
        // Things at the global scope are always "members" of their module.
        //
        if(parentDecl->As<ModuleDecl>())
            return false;

        // Anything explicitly marked `static` and not at module scope
        // counts as a static rather than instance declaration.
        //
        if(decl->HasModifier<HLSLStaticModifier>())
            return true;

        // Next we need to deal with cases where a declaration is
        // effectively `static` even if the language doesn't make
        // the user say so. Most languages make the default assumption
        // that nested types are `static` even if they don't say
        // so (Java is an exception here, perhaps due to some
        // influence from the Scandanavian OOP tradition of Beta/gbeta).
        //
        if(dynamic_cast<AggTypeDecl*>(decl))
            return true;
        if(dynamic_cast<SimpleTypeDecl*>(decl))
            return true;

        // Things nested inside functions may have dependencies
        // on values from the enclosing scope, but this needs to
        // be dealt with via "capture" so they are also effectively
        // `static`
        //
        if(dynamic_cast<FunctionDeclBase*>(parentDecl))
            return true;

        // Type constraint declarations are used in member-reference
        // context as a form of casting operation, so we treat them
        // as if they are instance members. This is a bit of a hack,
        // but it achieves the result we want until we have an
        // explicit representation of up-cast operations in the
        // AST.
        //
        if(decl->As<TypeConstraintDecl>())
            return false;

        return false;
    }

        /// Should the given `decl` be treated as a static rather than instance declaration?
    bool isEffectivelyStatic(
        Decl*           decl)
    {
        // For the purposes of an ordinary declaration, when determining if
        // it is static or per-instance, the "parent" declaration we really
        // care about is the next outer non-generic declaration.
        //
        // TODO: This idiom of getting the "next outer non-generic declaration"
        // comes up just enough that we should probably have a convenience
        // function for it.

        auto parentDecl = decl->ParentDecl;
        if(auto genericDecl = parentDecl->As<GenericDecl>())
            parentDecl = genericDecl->ParentDecl;

        return isEffectivelyStatic(decl, parentDecl);
    }

    // A flat representation of basic types (scalars, vectors and matrices)
    // that can be used as lookup key in caches
    struct BasicTypeKey
    {
        union
        {
            struct
            {
                unsigned char type : 4;
                unsigned char dim1 : 2;
                unsigned char dim2 : 2;
            } data;
            unsigned char aggVal;
        };
        bool fromType(Type* typeIn)
        {
            aggVal = 0;
            if (auto basicType = typeIn->AsBasicType())
            {
                data.type = (unsigned char)basicType->baseType;
                data.dim1 = data.dim2 = 0;
            }
            else if (auto vectorType = typeIn->AsVectorType())
            {
                if (auto elemCount = vectorType->elementCount.As<ConstantIntVal>())
                {
                    data.dim1 = elemCount->value - 1;
                    data.type = (unsigned char)vectorType->elementType->AsBasicType()->baseType;
                    data.dim2 = 0;
                }
                else
                    return false;
            }
            else if (auto matrixType = typeIn->AsMatrixType())
            {
                if (auto elemCount1 = dynamic_cast<ConstantIntVal*>(matrixType->getRowCount()))
                {
                    if (auto elemCount2 = dynamic_cast<ConstantIntVal*>(matrixType->getColumnCount()))
                    {
                        data.type = (unsigned char)matrixType->getElementType()->AsBasicType()->baseType;
                        data.dim1 = elemCount1->value - 1;
                        data.dim2 = elemCount2->value - 1;
                    }
                }
                else
                    return false;
            }
            else
                return false;
            return true;
        }
    };

    struct BasicTypeKeyPair
    {
        BasicTypeKey type1, type2;
        bool operator == (BasicTypeKeyPair p)
        {
            return type1.aggVal == p.type1.aggVal && type2.aggVal == p.type2.aggVal;
        }
        int GetHashCode()
        {
            return combineHash(type1.aggVal, type2.aggVal);
        }
    };

    struct OverloadCandidate
    {
        enum class Flavor
        {
            Func,
            Generic,
            UnspecializedGeneric,
        };
        Flavor flavor;

        enum class Status
        {
            GenericArgumentInferenceFailed,
            Unchecked,
            ArityChecked,
            FixityChecked,
            TypeChecked,
            DirectionChecked,
            Appicable,
        };
        Status status = Status::Unchecked;

        // Reference to the declaration being applied
        LookupResultItem item;

        // The type of the result expression if this candidate is selected
        RefPtr<Type>	resultType;

        // A system for tracking constraints introduced on generic parameters
        //            ConstraintSystem constraintSystem;

        // How much conversion cost should be considered for this overload,
        // when ranking candidates.
        ConversionCost conversionCostSum = kConversionCost_None;

        // When required, a candidate can store a pre-checked list of
        // arguments so that we don't have to repeat work across checking
        // phases. Currently this is only needed for generics.
        RefPtr<Substitutions>   subst;
    };

    struct OperatorOverloadCacheKey
    {
        IROp operatorName;
        BasicTypeKey args[2];
        bool operator == (OperatorOverloadCacheKey key)
        {
            return operatorName == key.operatorName && args[0].aggVal == key.args[0].aggVal
                && args[1].aggVal == key.args[1].aggVal;
        }
        int GetHashCode()
        {
            return ((int)(UInt64)(void*)(operatorName) << 16) ^ (args[0].aggVal << 8) ^ (args[1].aggVal);
        }
        bool fromOperatorExpr(OperatorExpr* opExpr)
        {
            // First, lets see if the argument types are ones
            // that we can encode in our space of keys.
            args[0].aggVal = 0;
            args[1].aggVal = 0;
            if (opExpr->Arguments.Count() > 2)
                return false;

            for (UInt i = 0; i < opExpr->Arguments.Count(); i++)
            {
                if (!args[i].fromType(opExpr->Arguments[i]->type.Ptr()))
                    return false;
            }

            // Next, lets see if we can find an intrinsic opcode
            // attached to an overloaded definition (filtered for
            // definitions that could conceivably apply to us).
            //
            // TODO: This should really be pased on the operator name
            // plus fixity, rather than the intrinsic opcode...
            //
            // We will need to reject postfix definitions for prefix
            // operators, and vice versa, to ensure things work.
            //
            auto prefixExpr = opExpr->As<PrefixExpr>();
            auto postfixExpr = opExpr->As<PostfixExpr>();

            if (auto overloadedBase = opExpr->FunctionExpr->As<OverloadedExpr>())
            {
                for(auto item : overloadedBase->lookupResult2 )
                {
                    // Look at a candidate definition to be called and
                    // see if it gives us a key to work with.
                    //
                    Decl* funcDecl = overloadedBase->lookupResult2.item.declRef.decl;
                    if (auto genDecl = funcDecl->As<GenericDecl>())
                        funcDecl = genDecl->inner.Ptr();

                    // Reject definitions that have the wrong fixity.
                    //
                    if(prefixExpr && !funcDecl->FindModifier<PrefixModifier>())
                        continue;
                    if(postfixExpr && !funcDecl->FindModifier<PostfixModifier>())
                        continue;

                    if (auto intrinsicOp = funcDecl->FindModifier<IntrinsicOpModifier>())
                    {
                        operatorName = intrinsicOp->op;
                        return true;
                    }
                }
            }
            return false;
        }
    };

    struct TypeCheckingCache
    {
        Dictionary<OperatorOverloadCacheKey, OverloadCandidate> resolvedOperatorOverloadCache;
        Dictionary<BasicTypeKeyPair, ConversionCost> conversionCostCache;
    };

    TypeCheckingCache* Session::getTypeCheckingCache()
    {
        if (!typeCheckingCache)
            typeCheckingCache = new TypeCheckingCache();
        return typeCheckingCache;
    }

    void Session::destroyTypeCheckingCache()
    {
        delete typeCheckingCache;
        typeCheckingCache = nullptr;
    }

    enum class CheckingPhase
    {
        Header, Body
    };

    struct SemanticsVisitor
        : ExprVisitor<SemanticsVisitor, RefPtr<Expr>>
        , StmtVisitor<SemanticsVisitor>
        , DeclVisitor<SemanticsVisitor>
    {
        CheckingPhase checkingPhase = CheckingPhase::Header;
        DeclCheckState getCheckedState()
        {
            if (checkingPhase == CheckingPhase::Body)
                return DeclCheckState::Checked;
            else
                return DeclCheckState::CheckedHeader;
        }
        DiagnosticSink* sink = nullptr;
        DiagnosticSink* getSink()
        {
            return sink;
        }

//        ModuleDecl * program = nullptr;
        FuncDecl * function = nullptr;

        CompileRequest* request = nullptr;
        TranslationUnitRequest* translationUnit = nullptr;

        SourceLanguage getSourceLanguage()
        {
            return translationUnit->sourceLanguage;
        }

        // lexical outer statements
        List<Stmt*> outerStmts;

        // We need to track what has been `import`ed,
        // to avoid importing the same thing more than once
        //
        // TODO: a smarter approach might be to filter
        // out duplicate references during lookup.
        HashSet<ModuleDecl*> importedModules;

    public:
        SemanticsVisitor(
            DiagnosticSink*         sink,
            CompileRequest*         request,
            TranslationUnitRequest* translationUnit)
            : sink(sink)
            , request(request)
            , translationUnit(translationUnit)
        {
        }

        CompileRequest* getCompileRequest() { return request; }
        TranslationUnitRequest* getTranslationUnit() { return translationUnit; }
        Session* getSession()
        {
            return getCompileRequest()->mSession;
        }

    public:
        // Translate Types
        RefPtr<Type> typeResult;
        RefPtr<Expr> TranslateTypeNodeImpl(const RefPtr<Expr> & node)
        {
            if (!node) return nullptr;

            auto expr = CheckTerm(node);
            expr = ExpectATypeRepr(expr);
            return expr;
        }
        RefPtr<Type> ExtractTypeFromTypeRepr(const RefPtr<Expr>& typeRepr)
        {
            if (!typeRepr) return nullptr;
            if (auto typeType = typeRepr->type->As<TypeType>())
            {
                return typeType->type;
            }
            return getSession()->getErrorType();
        }
        RefPtr<Type> TranslateTypeNode(const RefPtr<Expr> & node)
        {
            if (!node) return nullptr;
            auto typeRepr = TranslateTypeNodeImpl(node);
            return ExtractTypeFromTypeRepr(typeRepr);
        }
        TypeExp TranslateTypeNodeForced(TypeExp const& typeExp)
        {
            auto typeRepr = TranslateTypeNodeImpl(typeExp.exp);

            TypeExp result;
            result.exp = typeRepr;
            result.type = ExtractTypeFromTypeRepr(typeRepr);
            return result;
        }
        TypeExp TranslateTypeNode(TypeExp const& typeExp)
        {
            // HACK(tfoley): It seems that in some cases we end up re-checking
            // syntax that we've already checked. We need to root-cause that
            // issue, but for now a quick fix in this case is to early
            // exist if we've already got a type associated here:
            if (typeExp.type)
            {
                return typeExp;
            }
            return TranslateTypeNodeForced(typeExp);
        }

        RefPtr<DeclRefType> getExprDeclRefType(Expr * expr)
        {
            if (auto typetype = expr->type->As<TypeType>())
                return typetype->type.As<DeclRefType>();
            else
                return expr->type->As<DeclRefType>();
        }

        /// Is `decl` usable as a static member?
        bool isDeclUsableAsStaticMember(
            Decl*   decl)
        {
            if(decl->HasModifier<HLSLStaticModifier>())
                return true;

            if(decl->As<ConstructorDecl>())
                return true;

            if(decl->As<EnumCaseDecl>())
                return true;

            if(decl->As<AggTypeDeclBase>())
                return true;

            if(decl->As<SimpleTypeDecl>())
                return true;

            return false;
        }

        /// Is `item` usable as a static member?
        bool isUsableAsStaticMember(
            LookupResultItem const& item)
        {
            // There's a bit of a gotcha here, because a lookup result
            // item might include "breadcrumbs" that indicate more steps
            // along the lookup path. As a result it isn't always
            // valid to just check whether the final decl is usable
            // as a static member, because it might not even be a
            // member of the thing we are trying to work with.
            //

            Decl* decl = item.declRef.getDecl();
            for(auto bb = item.breadcrumbs; bb; bb = bb->next)
            {
                switch(bb->kind)
                {
                // In case lookup went through a `__transparent` member,
                // we are interested in the static-ness of that transparent
                // member, and *not* the static-ness of whatever was inside
                // of it.
                //
                // TODO: This would need some work if we ever had
                // transparent *type* members.
                //
                case LookupResultItem::Breadcrumb::Kind::Member:
                    decl = bb->declRef.getDecl();
                    break;

                // TODO: Are there any other cases that need special-case
                // handling here?

                default:
                    break;
                }
            }

            // Okay, we've found the declaration we should actually
            // be checking, so lets validate that.

            return isDeclUsableAsStaticMember(decl);
        }

        RefPtr<Expr> ConstructDeclRefExpr(
            DeclRef<Decl>   declRef,
            RefPtr<Expr>    baseExpr,
            SourceLoc       loc)
        {
            // Compute the type that this declaration reference will have in context.
            //
            auto type = GetTypeForDeclRef(declRef);

            // Construct an appropriate expression based on teh structured of
            // the declaration reference.
            //
            if (baseExpr)
            {
                // If there was a base expression, we will have some kind of
                // member expression.
                //
                if (baseExpr->type->As<TypeType>())
                {
                    auto expr = new StaticMemberExpr();
                    expr->loc = loc;
                    expr->type = type;
                    expr->BaseExpression = baseExpr;
                    expr->name = declRef.GetName();
                    expr->declRef = declRef;
                    return expr;
                }
                else if(isEffectivelyStatic(declRef.getDecl()))
                {
                    // Extract the type of the baseExpr
                    auto baseExprType = baseExpr->type.type;
                    RefPtr<SharedTypeExpr> baseTypeExpr = new SharedTypeExpr();
                    baseTypeExpr->base.type = baseExprType;
                    baseTypeExpr->type = new TypeType(baseExprType);

                    auto expr = new StaticMemberExpr();
                    expr->loc = loc;
                    expr->type = type;
                    expr->BaseExpression = baseTypeExpr;
                    expr->name = declRef.GetName();
                    expr->declRef = declRef;
                    return expr;
                }
                else
                {
                    // If the base expression wasn't a type, then this
                    // is a normal member expression.
                    //
                    auto expr = new MemberExpr();
                    expr->loc = loc;
                    expr->type = type;
                    expr->BaseExpression = baseExpr;
                    expr->name = declRef.GetName();
                    expr->declRef = declRef;

                    // When referring to a member through an expression,
                    // the result is only an l-value if both the base
                    // expression and the member agree that it should be.
                    //
                    // We have already used the `QualType` from the member
                    // above (that is `type`), so we need to take the
                    // l-value status of the base expression into account now.
                    if(!baseExpr->type.IsLeftValue)
                    {
                        expr->type.IsLeftValue = false;
                    }

                    return expr;
                }
            }
            else
            {
                // If there is no base expression, then the result must
                // be an ordinary variable expression.
                //
                auto expr = new VarExpr();
                expr->loc = loc;
                expr->name = declRef.GetName();
                expr->type = type;
                expr->declRef = declRef;
                return expr;
            }
        }

        RefPtr<Expr> ConstructDerefExpr(
            RefPtr<Expr>    base,
            SourceLoc       loc)
        {
            auto ptrLikeType = base->type->As<PointerLikeType>();
            SLANG_ASSERT(ptrLikeType);

            auto derefExpr = new DerefExpr();
            derefExpr->loc = loc;
            derefExpr->base = base;
            derefExpr->type = QualType(ptrLikeType->elementType);

            // TODO(tfoley): handle l-value status here

            return derefExpr;
        }

        RefPtr<Expr> createImplicitThisMemberExpr(
            Type*                                           type,
            SourceLoc                                       loc,
            LookupResultItem::Breadcrumb::ThisParameterMode thisParameterMode)
        {
            RefPtr<ThisExpr> expr = new ThisExpr();
            expr->type = type;
            expr->type.IsLeftValue = thisParameterMode == LookupResultItem::Breadcrumb::ThisParameterMode::Mutating;
            expr->loc = loc;
            return expr;
        }

        RefPtr<Expr> ConstructLookupResultExpr(
            LookupResultItem const& item,
            RefPtr<Expr>            baseExpr,
            SourceLoc               loc)
        {
            // If we collected any breadcrumbs, then these represent
            // additional segments of the lookup path that we need
            // to expand here.
            auto bb = baseExpr;
            for (auto breadcrumb = item.breadcrumbs; breadcrumb; breadcrumb = breadcrumb->next)
            {
                switch (breadcrumb->kind)
                {
                case LookupResultItem::Breadcrumb::Kind::Member:
                    bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc);
                    break;

                case LookupResultItem::Breadcrumb::Kind::Deref:
                    bb = ConstructDerefExpr(bb, loc);
                    break;

                case LookupResultItem::Breadcrumb::Kind::Constraint:
                    {
                        // TODO: do we need to make something more
                        // explicit here?
                        bb = ConstructDeclRefExpr(
                            breadcrumb->declRef,
                            bb,
                            loc);
                    }
                    break;

                case LookupResultItem::Breadcrumb::Kind::This:
                    {
                        // We expect a `this` to always come
                        // at the start of a chain.
                        SLANG_ASSERT(bb == nullptr);

                        // The member was looked up via a `this` expression,
                        // so we need to create one here.
                        if (auto extensionDeclRef = breadcrumb->declRef.As<ExtensionDecl>())
                        {
                            bb = createImplicitThisMemberExpr(
                                GetTargetType(extensionDeclRef),
                                loc,
                                breadcrumb->thisParameterMode);
                        }
                        else
                        {
                            auto type = DeclRefType::Create(getSession(), breadcrumb->declRef);
                            bb = createImplicitThisMemberExpr(
                                type,
                                loc,
                                breadcrumb->thisParameterMode);
                        }
                    }
                    break;

                default:
                    SLANG_UNREACHABLE("all cases handle");
                }
            }

            return ConstructDeclRefExpr(item.declRef, bb, loc);
        }

        RefPtr<Expr> createLookupResultExpr(
            LookupResult const&     lookupResult,
            RefPtr<Expr>            baseExpr,
            SourceLoc               loc)
        {
            if (lookupResult.isOverloaded())
            {
                auto overloadedExpr = new OverloadedExpr();
                overloadedExpr->loc = loc;
                overloadedExpr->type = QualType(
                    getSession()->getOverloadedType());
                overloadedExpr->base = baseExpr;
                overloadedExpr->lookupResult2 = lookupResult;
                return overloadedExpr;
            }
            else
            {
                return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc);
            }
        }

        RefPtr<Expr> ResolveOverloadedExpr(RefPtr<OverloadedExpr> overloadedExpr, LookupMask mask)
        {
            auto lookupResult = overloadedExpr->lookupResult2;
            SLANG_RELEASE_ASSERT(lookupResult.isValid() && lookupResult.isOverloaded());

            // Take the lookup result we had, and refine it based on what is expected in context.
            lookupResult = refineLookup(lookupResult, mask);

            if (!lookupResult.isValid())
            {
                // If we didn't find any symbols after filtering, then just
                // use the original and report errors that way
                return overloadedExpr;
            }

            if (lookupResult.isOverloaded())
            {
                // We had an ambiguity anyway, so report it.
                getSink()->diagnose(overloadedExpr, Diagnostics::ambiguousReference, lookupResult.items[0].declRef.GetName());

                for(auto item : lookupResult.items)
                {
                    String declString = getDeclSignatureString(item);
                    getSink()->diagnose(item.declRef, Diagnostics::overloadCandidate, declString);
                }

                // TODO(tfoley): should we construct a new ErrorExpr here?
                return CreateErrorExpr(overloadedExpr);
            }

            // otherwise, we had a single decl and it was valid, hooray!
            return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr->loc);
        }

        RefPtr<Expr> ExpectATypeRepr(RefPtr<Expr> expr)
        {
            if (auto overloadedExpr = expr.As<OverloadedExpr>())
            {
                expr = ResolveOverloadedExpr(overloadedExpr, LookupMask::type);
            }

            if (auto typeType = expr->type.type->As<TypeType>())
            {
                return expr;
            }
            else if (auto errorType = expr->type.type->As<ErrorType>())
            {
                return expr;
            }

            getSink()->diagnose(expr, Diagnostics::unimplemented, "expected a type");
            return CreateErrorExpr(expr);
        }

        RefPtr<Type> ExpectAType(RefPtr<Expr> expr)
        {
            auto typeRepr = ExpectATypeRepr(expr);
            if (auto typeType = typeRepr->type->As<TypeType>())
            {
                return typeType->type;
            }
            return getSession()->getErrorType();
        }

        RefPtr<Type> ExtractGenericArgType(RefPtr<Expr> exp)
        {
            return ExpectAType(exp);
        }

        RefPtr<IntVal> ExtractGenericArgInteger(RefPtr<Expr> exp)
        {
            return CheckIntegerConstantExpression(exp.Ptr());
        }

        RefPtr<Val> ExtractGenericArgVal(RefPtr<Expr> exp)
        {
            if (auto overloadedExpr = exp.As<OverloadedExpr>())
            {
                // assume that if it is overloaded, we want a type
                exp = ResolveOverloadedExpr(overloadedExpr, LookupMask::type);
            }

            if (auto typeType = exp->type->As<TypeType>())
            {
                return typeType->type;
            }
            else if (auto errorType = exp->type->As<ErrorType>())
            {
                return exp->type.type;
            }
            else
            {
                return ExtractGenericArgInteger(exp);
            }
        }

        // Construct a type reprsenting the instantiation of
        // the given generic declaration for the given arguments.
        // The arguments should already be checked against
        // the declaration.
        RefPtr<Type> InstantiateGenericType(
            DeclRef<GenericDecl>        genericDeclRef,
            List<RefPtr<Expr>> const&   args)
        {
            RefPtr<GenericSubstitution> subst = new GenericSubstitution();
            subst->genericDecl = genericDeclRef.getDecl();
            subst->outer = genericDeclRef.substitutions.substitutions;

            for (auto argExpr : args)
            {
                subst->args.Add(ExtractGenericArgVal(argExpr));
            }

            DeclRef<Decl> innerDeclRef;
            innerDeclRef.decl = GetInner(genericDeclRef);
            innerDeclRef.substitutions = SubstitutionSet(subst);

            return DeclRefType::Create(
                getSession(),
                innerDeclRef);
        }

        // This routine is a bottleneck for all declaration checking,
        // so that we can add some quality-of-life features for users
        // in cases where the compiler crashes
        void dispatchDecl(DeclBase* decl)
        {
            try
            {
                DeclVisitor::dispatch(decl);
            }
            // Don't emit any context message for an explicit `AbortCompilationException`
            // because it should only happen when an error is already emitted.
            catch(AbortCompilationException&) { throw; }
            catch(...)
            {
                getCompileRequest()->noteInternalErrorLoc(decl->loc);
                throw;
            }
        }
        void dispatchStmt(Stmt* stmt)
        {
            try
            {
                StmtVisitor::dispatch(stmt);
            }
            catch(AbortCompilationException&) { throw; }
            catch(...)
            {
                getCompileRequest()->noteInternalErrorLoc(stmt->loc);
                throw;
            }
        }
        void dispatchExpr(Expr* expr)
        {
            try
            {
                ExprVisitor::dispatch(expr);
            }
            catch(AbortCompilationException&) { throw; }
            catch(...)
            {
                getCompileRequest()->noteInternalErrorLoc(expr->loc);
                throw;
            }
        }

        // Make sure a declaration has been checked, so we can refer to it.
        // Note that this may lead to us recursively invoking checking,
        // so this may not be the best way to handle things.
        void EnsureDecl(RefPtr<Decl> decl, DeclCheckState state)
        {
            if (decl->IsChecked(state)) return;
            if (decl->checkState == DeclCheckState::CheckingHeader)
            {
                // We tried to reference the same declaration while checking it!
                //
                // TODO: we should ideally be tracking a "chain" of declarations
                // being checked on the stack, so that we can report the full
                // chain that leads from this declaration back to itself.
                //
                sink->diagnose(decl, Diagnostics::cyclicReference, decl);
                return;
            }

            // Hack: if we are somehow referencing a local variable declaration
            // before the line of code that defines it, then we need to diagnose
            // an error.
            //
            // TODO: The right answer is that lookup should have been performed in
            // the scope that was in place *before* the variable was declared, but
            // this is a quick fix that at least alerts the user to how we are
            // interpreting their code.
            if (auto varDecl = decl.As<Variable>())
            {
                if (auto parenScope = varDecl->ParentDecl->As<ScopeDecl>())
                {
                    // TODO: This diagnostic should be emitted on the line that is referencing
                    // the declaration. That requires `EnsureDecl` to take the requesting
                    // location as a parameter.
                    sink->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl);
                    return;
                }
            }

            if (DeclCheckState::CheckingHeader > decl->checkState)
            {
                decl->SetCheckState(DeclCheckState::CheckingHeader);
            }

            // Check the modifiers on the declaration first, in case
            // semantics of the body itself will depend on them.
            checkModifiers(decl);

            // Use visitor pattern to dispatch to correct case
            dispatchDecl(decl);

            if(state > decl->checkState)
            {
                decl->SetCheckState(state);
            }
        }

        void EnusreAllDeclsRec(RefPtr<Decl> decl)
        {
            checkDecl(decl);
            if (auto containerDecl = decl.As<ContainerDecl>())
            {
                for (auto m : containerDecl->Members)
                {
                    EnusreAllDeclsRec(m);
                }
            }
        }

        // A "proper" type is one that can be used as the type of an expression.
        // Put simply, it can be a concrete type like `int`, or a generic
        // type that is applied to arguments, like `Texture2D<float4>`.
        // The type `void` is also a proper type, since we can have expressions
        // that return a `void` result (e.g., many function calls).
        //
        // A "non-proper" type is any type that can't actually have values.
        // A simple example of this in C++ is `std::vector` - you can't have
        // a value of this type.
        //
        // Part of what this function does is give errors if somebody tries
        // to use a non-proper type as the type of a variable (or anything
        // else that needs a proper type).
        //
        // The other thing it handles is the fact that HLSL lets you use
        // the name of a non-proper type, and then have the compiler fill
        // in the default values for its type arguments (e.g., a variable
        // given type `Texture2D` will actually have type `Texture2D<float4>`).
        bool CoerceToProperTypeImpl(
            TypeExp const&  typeExp,
            RefPtr<Type>*   outProperType,
            DiagnosticSink* diagSink)
        {
            Type* type = typeExp.type.Ptr();
            if(!type && typeExp.exp)
            {
                if(auto typeType = typeExp.exp->type.type.As<TypeType>())
                {
                    type = typeType->type;
                }
            }

            if (auto genericDeclRefType = type->As<GenericDeclRefType>())
            {
                // We are using a reference to a generic declaration as a concrete
                // type. This means we should substitute in any default parameter values
                // if they are available.
                //
                // TODO(tfoley): A more expressive type system would substitute in
                // "fresh" variables and then solve for their values...
                //

                auto genericDeclRef = genericDeclRefType->GetDeclRef();
                checkDecl(genericDeclRef.decl);
                List<RefPtr<Expr>> args;
                for (RefPtr<Decl> member : genericDeclRef.getDecl()->Members)
                {
                    if (auto typeParam = member.As<GenericTypeParamDecl>())
                    {
                        if (!typeParam->initType.exp)
                        {
                            if (diagSink)
                            {
                                diagSink->diagnose(typeExp.exp.Ptr(), Diagnostics::genericTypeNeedsArgs, typeExp);
                                *outProperType = getSession()->getErrorType();
                            }
                            return false;
                        }

                        // TODO: this is one place where syntax should get cloned!
                        if(outProperType)
                            args.Add(typeParam->initType.exp);
                    }
                    else if (auto valParam = member.As<GenericValueParamDecl>())
                    {
                        if (!valParam->initExpr)
                        {
                            if (diagSink)
                            {
                                diagSink->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter");
                                *outProperType = getSession()->getErrorType();
                            }
                            return false;
                        }

                        // TODO: this is one place where syntax should get cloned!
                        if(outProperType)
                            args.Add(valParam->initExpr);
                    }
                    else
                    {
                        // ignore non-parameter members
                    }
                }

                if (outProperType)
                {
                    *outProperType = InstantiateGenericType(genericDeclRef, args);
                }
                return true;
            }
            else
            {
                // default case: we expect this to already be a proper type
                if (outProperType)
                {
                    *outProperType = type;
                }
                return true;
            }
        }



        TypeExp CoerceToProperType(TypeExp const& typeExp)
        {
            TypeExp result = typeExp;
            CoerceToProperTypeImpl(typeExp, &result.type, getSink());
            return result;
        }

        TypeExp tryCoerceToProperType(TypeExp const& typeExp)
        {
            TypeExp result = typeExp;
            if(!CoerceToProperTypeImpl(typeExp, &result.type, nullptr))
                return TypeExp();
            return result;
        }

        // Check a type, and coerce it to be proper
        TypeExp CheckProperType(TypeExp typeExp)
        {
            return CoerceToProperType(TranslateTypeNode(typeExp));
        }

        // For our purposes, a "usable" type is one that can be
        // used to declare a function parameter, variable, etc.
        // These turn out to be all the proper types except
        // `void`.
        //
        // TODO(tfoley): consider just allowing `void` as a
        // simple example of a "unit" type, and get rid of
        // this check.
        TypeExp CoerceToUsableType(TypeExp const& typeExp)
        {
            TypeExp result = CoerceToProperType(typeExp);
            Type* type = result.type.Ptr();
            if (auto basicType = type->As<BasicExpressionType>())
            {
                // TODO: `void` shouldn't be a basic type, to make this easier to avoid
                if (basicType->baseType == BaseType::Void)
                {
                    // TODO(tfoley): pick the right diagnostic message
                    getSink()->diagnose(result.exp.Ptr(), Diagnostics::invalidTypeVoid);
                    result.type = getSession()->getErrorType();
                    return result;
                }
            }
            return result;
        }

        // Check a type, and coerce it to be usable
        TypeExp CheckUsableType(TypeExp typeExp)
        {
            return CoerceToUsableType(TranslateTypeNode(typeExp));
        }

        RefPtr<Expr> CheckTerm(RefPtr<Expr> term)
        {
            if (!term) return nullptr;
            return ExprVisitor::dispatch(term);
        }

        RefPtr<Expr> CreateErrorExpr(Expr* expr)
        {
            expr->type = QualType(getSession()->getErrorType());
            return expr;
        }

        bool IsErrorExpr(RefPtr<Expr> expr)
        {
            // TODO: we may want other cases here...

            if (auto errorType = expr->type->As<ErrorType>())
                return true;

            return false;
        }

        // Capture the "base" expression in case this is a member reference
        RefPtr<Expr> GetBaseExpr(RefPtr<Expr> expr)
        {
            if (auto memberExpr = expr.As<MemberExpr>())
            {
                return memberExpr->BaseExpression;
            }
            else if(auto overloadedExpr = expr.As<OverloadedExpr>())
            {
                return overloadedExpr->base;
            }
            return nullptr;
        }

    public:

        bool ValuesAreEqual(
            RefPtr<IntVal> left,
            RefPtr<IntVal> right)
        {
            if(left == right) return true;

            if(auto leftConst = left.As<ConstantIntVal>())
            {
                if(auto rightConst = right.As<ConstantIntVal>())
                {
                    return leftConst->value == rightConst->value;
                }
            }

            if(auto leftVar = left.As<GenericParamIntVal>())
            {
                if(auto rightVar = right.As<GenericParamIntVal>())
                {
                    return leftVar->declRef.Equals(rightVar->declRef);
                }
            }

            return false;
        }

        // Compute the cost of using a particular declaration to
        // perform implicit type conversion.
        ConversionCost getImplicitConversionCost(
            Decl* decl)
        {
            if(auto modifier = decl->FindModifier<ImplicitConversionModifier>())
            {
                return modifier->cost;
            }

            return kConversionCost_Explicit;
        }

        // Central engine for implementing implicit coercion logic
        bool TryCoerceImpl(
            RefPtr<Type>			toType,		// the target type for conversion
            RefPtr<Expr>*	outToExpr,	// (optional) a place to stuff the target expression
            RefPtr<Type>			fromType,	// the source type for the conversion
            RefPtr<Expr>	fromExpr,	// the source expression
            ConversionCost*					outCost)	// (optional) a place to stuff the conversion cost
        {
            // Easy case: the types are equal
            if (toType->Equals(fromType))
            {
                if (outToExpr)
                    *outToExpr = fromExpr;
                if (outCost)
                    *outCost = kConversionCost_None;
                return true;
            }

            // If either type is an error, then let things pass.
            if (toType->As<ErrorType>() || fromType->As<ErrorType>())
            {
                if (outToExpr)
                    *outToExpr = CreateImplicitCastExpr(toType, fromExpr);
                if (outCost)
                    *outCost = kConversionCost_None;
                return true;
            }

            // Coercion from an initializer list is allowed for many types
            if( auto fromInitializerListExpr = fromExpr.As<InitializerListExpr>())
            {
                auto argCount = fromInitializerListExpr->args.Count();

                // In the case where we need to build a reuslt expression,
                // we will collect the new arguments here
                List<RefPtr<Expr>> coercedArgs;

                if (auto toVecType = toType->As<VectorExpressionType>())
                {
                    auto toElementCount = toVecType->elementCount;
                    auto toElementType = toVecType->elementType;

                    UInt elementCount = 0;
                    if (auto constElementCount = toElementCount.As<ConstantIntVal>())
                    {
                        elementCount = (UInt) constElementCount->value;
                    }
                    else
                    {
                        // We don't know the element count statically,
                        // so what are we supposed to be doing?
                        elementCount = fromInitializerListExpr->args.Count();
                    }

                    // TODO: need to check that the element count
                    // for the vector type matches the argument
                    // count for the initializer list, or else
                    // fix them up to match.

                    for(auto arg : fromInitializerListExpr->args)
                    {
                        RefPtr<Expr> coercedArg;
                        ConversionCost argCost;

                        bool argResult = TryCoerceImpl(
                            toElementType,
                            outToExpr ? &coercedArg : nullptr,
                            arg->type,
                            arg,
                            outCost ? &argCost : nullptr);

                        // No point in trying further if any argument fails
                        if(!argResult)
                            return false;

                        // TODO(tfoley): what to do with cost?
                        // This only matters if/when we allow an initializer list as an argument to
                        // an overloaded call.

                        if( outToExpr )
                        {
                            coercedArgs.Add(coercedArg);
                        }
                    }
                }
                //
                // TODO(tfoley): How to handle matrices here?
                // Should they expect individual scalars, or support
                // vectors for the rows?
                //
                if(auto toDeclRefType = toType->As<DeclRefType>())
                {
                    auto toTypeDeclRef = toDeclRefType->declRef;
                    if(auto toStructDeclRef = toTypeDeclRef.As<StructDecl>())
                    {
                        // Trying to initialize a `struct` type given an initializer list.
                        // We will go through the fields in order and try to match them
                        // up with initializer arguments.


                        UInt argIndex = 0;
                        for(auto fieldDeclRef : getMembersOfType<StructField>(toStructDeclRef))
                        {
                            if(argIndex >= argCount)
                            {
                                // We've consumed all the arguments, so we should stop
                                break;
                            }

                            auto arg = fromInitializerListExpr->args[argIndex++];

                            //
                            RefPtr<Expr> coercedArg;
                            ConversionCost argCost;

                            bool argResult = TryCoerceImpl(
                                GetType(fieldDeclRef),
                                outToExpr ? &coercedArg : nullptr,
                                arg->type,
                                arg,
                                outCost ? &argCost : nullptr);

                            // No point in trying further if any argument fails
                            if(!argResult)
                                return false;

                            // TODO(tfoley): what to do with cost?
                            // This only matters if/when we allow an initializer list as an argument to
                            // an overloaded call.

                            if( outToExpr )
                            {
                                coercedArgs.Add(coercedArg);
                            }
                        }
                    }
                }
                else if(auto toArrayType = toType->As<ArrayExpressionType>())
                {
                    // TODO(tfoley): If we can compute the size of the array statically,
                    // then we want to check that there aren't too many initializers present

                    auto toElementType = toArrayType->baseType;

                    for(auto& arg : fromInitializerListExpr->args)
                    {
                        RefPtr<Expr> coercedArg;
                        ConversionCost argCost;

                        bool argResult = TryCoerceImpl(
                                toElementType,
                                outToExpr ? &coercedArg : nullptr,
                                arg->type,
                                arg,
                                outCost ? &argCost : nullptr);

                        // No point in trying further if any argument fails
                        if(!argResult)
                            return false;

                        if( outToExpr )
                        {
                            coercedArgs.Add(coercedArg);
                        }
                    }
                }
                else
                {
                    // By default, we don't allow a type to be initialized using
                    // an initializer list.
                    return false;
                }

                // For now, coercion from an initializer list has no cost
                if(outCost)
                {
                    *outCost = kConversionCost_None;
                }

                // We were able to coerce all the arguments given, and so
                // we need to construct a suitable expression to remember the result
                if(outToExpr)
                {
                    auto toInitializerListExpr = new InitializerListExpr();
                    toInitializerListExpr->loc = fromInitializerListExpr->loc;
                    toInitializerListExpr->type = QualType(toType);
                    toInitializerListExpr->args = coercedArgs;


                    *outToExpr = toInitializerListExpr;
                }

                return true;
            }

            //
            if (auto toDeclRefType = toType->As<DeclRefType>())
            {
                auto toTypeDeclRef = toDeclRefType->declRef;
                if (auto interfaceDeclRef = toTypeDeclRef.As<InterfaceDecl>())
                {
                    // Trying to convert to an interface type.
                    //
                    // We will allow this if the type conforms to the interface.
                    if (DoesTypeConformToInterface(fromType, interfaceDeclRef))
                    {
                        if (outToExpr)
                            *outToExpr = CreateImplicitCastExpr(toType, fromExpr);
                        if (outCost)
                            *outCost = kConversionCost_CastToInterface;
                        return true;
                    }
                }
                else if (auto genParamDeclRef = toTypeDeclRef.As<GenericTypeParamDecl>())
                {
                    // We need to enumerate the constraints placed on this type by its outer
                    // generic declaration, and see if any of them guarantees that we
                    // satisfy the given interface..
                    auto genericDeclRef = genParamDeclRef.GetParent().As<GenericDecl>();
                    SLANG_ASSERT(genericDeclRef);

                    for (auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef))
                    {
                        auto sub = GetSub(constraintDeclRef);
                        auto sup = GetSup(constraintDeclRef);

                        auto subDeclRef = sub->As<DeclRefType>();
                        if (!subDeclRef)
                            continue;
                        if (subDeclRef->declRef != genParamDeclRef)
                            continue;
                        auto supDeclRefType = sup->As<DeclRefType>();
                        if (supDeclRefType)
                        {
                            auto toInterfaceDeclRef = supDeclRefType->declRef.As<InterfaceDecl>();
                            if (DoesTypeConformToInterface(fromType, toInterfaceDeclRef))
                            {
                                if (outToExpr)
                                    *outToExpr = CreateImplicitCastExpr(toType, fromExpr);
                                if (outCost)
                                    *outCost = kConversionCost_CastToInterface;
                                return true;
                            }
                        }
                    }

                }

            }

            // Are we converting from a parameter group type to its element type?
            if(auto fromParameterGroupType = fromType->As<ParameterGroupType>())
            {
                auto fromElementType = fromParameterGroupType->getElementType();

                // If we have, e.g., `ConstantBuffer<A>` and we want to convert
                // to `B`, where conversion from `A` to `B` is possible, then
                // we will do so here.

                ConversionCost subCost = 0;
                if(CanCoerce(toType, fromElementType, &subCost))
                {
                    if(outCost)
                        *outCost = subCost + kConversionCost_ImplicitDereference;

                    if(outToExpr)
                    {
                        auto derefExpr = new DerefExpr();
                        derefExpr->base = fromExpr;
                        derefExpr->type = QualType(fromElementType);

                        return TryCoerceImpl(
                            toType,
                            outToExpr,
                            fromElementType,
                            derefExpr,
                            nullptr);
                    }
                    return true;
                }
            }


            // Look for an initializer/constructor declaration in the target type,
            // which is marked as usable for implicit conversion, and which takes
            // the source type as an argument.

            OverloadResolveContext overloadContext;

            overloadContext.disallowNestedConversions = true;
            overloadContext.argCount = 1;
            overloadContext.argTypes = &fromType;

            overloadContext.originalExpr = nullptr;
            if(fromExpr)
            {
                overloadContext.loc = fromExpr->loc;
                overloadContext.funcLoc = fromExpr->loc;
                overloadContext.args = &fromExpr;
            }

            overloadContext.baseExpr = nullptr;
            overloadContext.mode = OverloadResolveContext::Mode::JustTrying;

            AddTypeOverloadCandidates(toType, overloadContext, toType);

            if(overloadContext.bestCandidates.Count() != 0)
            {
                // There were multiple candidates that were equally good.

                // First, we will check if these candidates are even applicable.
                // If they aren't, then they can't be used for conversion.
                if(overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Appicable)
                    return false;

                // If we reach this point, then we have multiple candidates which are
                // all equally applicable, which means we have an ambiguity.
                // If the user is just querying whether a conversion is possible, we
                // will tell them it is, because ambiguity should trigger an ambiguity
                // error, and not a "no conversion possible" error.

                // We will compute a nominal conversion cost as the minimum over
                // all the conversions available.
                ConversionCost cost = kConversionCost_GeneralConversion;
                for(auto candidate : overloadContext.bestCandidates)
                {
                    ConversionCost candidateCost = getImplicitConversionCost(
                        candidate.item.declRef.getDecl());

                    if(candidateCost < cost)
                        cost = candidateCost;
                }

                if(outCost)
                    *outCost = cost;

                if(outToExpr)
                {
                    // The user is asking for us to actually perform the conversion,
                    // so we need to generate an appropriate expression here.

                    // YONGH: I am confused why we are not hitting this case before
                    //throw "foo bar baz";
                    // YONGH: temporary work around, may need to create the actual
                    // invocation expr to the constructor call
                    *outToExpr = fromExpr;
                }

                return true;
            }
            else if(overloadContext.bestCandidate)
            {
                // There is a single best candidate for conversion.

                // It might not actually be usable, so let's check that first.
                if(overloadContext.bestCandidate->status != OverloadCandidate::Status::Appicable)
                    return false;

                // Okay, it is applicable, and we just need to let the user
                // know about it, and optionally construct a call.

                // We need to extract the conversion cost from the candidate we found.
                ConversionCost cost = getImplicitConversionCost(
                        overloadContext.bestCandidate->item.declRef.getDecl());;

                if(outCost)
                    *outCost = cost;

                if(outToExpr)
                {
                    // The logic here is a bit ugly, to deal with the fact that
                    // `CompleteOverloadCandidate` will, left to its own devices,
                    // construct a vanilla `InvokeExpr` to represent the call
                    // to the initializer we found, while we *want* it to
                    // create some variety of `ImplicitCastExpr`.
                    //
                    // Now, it just so happens that `CompleteOverloadCandidate`
                    // will use the "original" expression if one is available,
                    // so we'll create one and initialize it here.
                    // We fill in the location and arguments, but not the
                    // base expression (the callee), since that will come
                    // from the selected overload candidate.
                    //
                    auto castExpr = createImplicitCastExpr();
                    castExpr->loc = fromExpr->loc;
                    castExpr->Arguments.Add(fromExpr);
                    //
                    // Next we need to set our cast expression as the "original"
                    // expression and then complete the overload process.
                    //
                    overloadContext.originalExpr = castExpr;
                    *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate);
                    //
                    // However, the above isn't *quite* enough, because
                    // the process of completing the overload candidate
                    // might overwrite the argument list that was passed
                    // in to overload resolution, and in this case that
                    // "argument list" was just a pointer to `fromExpr`.
                    //
                    // That means we need to clear the argument list and
                    // reload it from `fromExpr` to make sure that we
                    // got the arguments *after* any transformations
                    // were applied.
                    // For right now this probably doesn't matter,
                    // because we don't allow nested implicit conversions,
                    // but I'd rather play it safe.
                    //
                    castExpr->Arguments.Clear();
                    castExpr->Arguments.Add(fromExpr);
                }

                return true;
            }

            return false;
        }

        // Check whether a type coercion is possible
        bool CanCoerce(
            RefPtr<Type>			toType,			// the target type for conversion
            RefPtr<Type>			fromType,		// the source type for the conversion
            ConversionCost*					outCost = 0)	// (optional) a place to stuff the conversion cost
        {
            BasicTypeKey key1, key2;
            BasicTypeKeyPair cacheKey;
            bool shouldAddToCache = false;
            ConversionCost cost;
            TypeCheckingCache* typeCheckingCache = getSession()->getTypeCheckingCache();
            if (key1.fromType(toType.Ptr()) && key2.fromType(fromType.Ptr()))
            {
                cacheKey.type1 = key1;
                cacheKey.type2 = key2;

                if (typeCheckingCache->conversionCostCache.TryGetValue(cacheKey, cost))
                {
                    if (outCost)
                        *outCost = cost;
                    return cost != kConversionCost_Impossible;
                }
                else
                    shouldAddToCache = true;
            }
            bool rs = TryCoerceImpl(
                toType,
                nullptr,
                fromType,
                nullptr,
                &cost);
            if (outCost)
                *outCost = cost;
            if (shouldAddToCache)
            {
                if (!rs)
                    cost = kConversionCost_Impossible;
                typeCheckingCache->conversionCostCache[cacheKey] = cost;
            }
            return rs;
        }

        RefPtr<TypeCastExpr> createImplicitCastExpr()
        {
            return new ImplicitCastExpr();
        }

        RefPtr<Expr> CreateImplicitCastExpr(
            RefPtr<Type>	toType,
            RefPtr<Expr>	fromExpr)
        {
            RefPtr<TypeCastExpr> castExpr = createImplicitCastExpr();

            auto typeType = new TypeType();
            typeType->setSession(getSession());
            typeType->type = toType;

            auto typeExpr = new SharedTypeExpr();
            typeExpr->type.type = typeType;
            typeExpr->base.type = toType;

            castExpr->loc = fromExpr->loc;
            castExpr->FunctionExpr = typeExpr;
            castExpr->type = QualType(toType);
            castExpr->Arguments.Add(fromExpr);
            return castExpr;
        }

        // Perform type coercion, and emit errors if it isn't possible
        RefPtr<Expr> Coerce(
            RefPtr<Type>			toType,
            RefPtr<Expr>	fromExpr)
        {
            RefPtr<Expr> expr;
            if (!TryCoerceImpl(
                toType,
                &expr,
                fromExpr->type.Ptr(),
                fromExpr.Ptr(),
                nullptr))
            {
                getSink()->diagnose(fromExpr->loc, Diagnostics::typeMismatch, toType, fromExpr->type);

                // Note(tfoley): We don't call `CreateErrorExpr` here, because that would
                // clobber the type on `fromExpr`, and an invariant here is that coercion
                // really shouldn't *change* the expression that is passed in, but should
                // introduce new AST nodes to coerce its value to a different type...
                return CreateImplicitCastExpr(
                    getSession()->getErrorType(),
                    fromExpr);
            }
            return expr;
        }

        void CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl)
        {
            // Check the type, if one was given
            TypeExp type = CheckUsableType(varDecl->type);

            // TODO: Additional validation rules on types should go here,
            // but we need to deal with the fact that some cases might be
            // allowed in one context (e.g., an unsized array parameter)
            // but not in othters (e.g., an unsized array field in a struct).

            // Check the initializers, if one was given
            RefPtr<Expr> initExpr = CheckTerm(varDecl->initExpr);

            // If a type was given, ...
            if (type.Ptr())
            {
                // then coerce any initializer to the type
                if (initExpr)
                {
                    initExpr = Coerce(type.Ptr(), initExpr);
                }
            }
            else
            {
                // TODO: infer a type from the initializers

                if (!initExpr)
                {
                    getSink()->diagnose(varDecl, Diagnostics::unimplemented, "variable declaration with no type must have initializer");
                }
                else
                {
                    getSink()->diagnose(varDecl, Diagnostics::unimplemented, "type inference for variable declaration");
                }
            }

            varDecl->type = type;
            varDecl->initExpr = initExpr;
        }

        // Fill in default substitutions for the 'subtype' part of a type constraint decl
        void CheckConstraintSubType(TypeExp & typeExp)
        {
            if (auto sharedTypeExpr = typeExp.exp.As<SharedTypeExpr>())
            {
                if (auto declRefType = sharedTypeExpr->base->AsDeclRefType())
                {
                    declRefType->declRef.substitutions = createDefaultSubstitutions(getSession(), declRefType->declRef.getDecl());
                    if (auto typetype = typeExp.exp->type.type.As<TypeType>())
                        typetype->type = declRefType;
                }
            }
        }

        void CheckGenericConstraintDecl(GenericTypeConstraintDecl* decl)
        {
            // TODO: are there any other validations we can do at this point?
            //
            // There probably needs to be a kind of "occurs check" to make
            // sure that the constraint actually applies to at least one
            // of the parameters of the generic.
            if (decl->checkState == DeclCheckState::Unchecked)
            {
                decl->checkState = getCheckedState();
                CheckConstraintSubType(decl->sub);
                decl->sub = TranslateTypeNodeForced(decl->sub);
                decl->sup = TranslateTypeNodeForced(decl->sup);
            }
        }

        void checkDecl(Decl* decl)
        {
            EnsureDecl(decl, checkingPhase == CheckingPhase::Header ? DeclCheckState::CheckedHeader : DeclCheckState::Checked);
        }

        void checkGenericDeclHeader(GenericDecl* genericDecl)
        {
            if (genericDecl->IsChecked(DeclCheckState::CheckedHeader))
                return;
            // check the parameters
            for (auto m : genericDecl->Members)
            {
                if (auto typeParam = m.As<GenericTypeParamDecl>())
                {
                    typeParam->initType = CheckProperType(typeParam->initType);
                }
                else if (auto valParam = m.As<GenericValueParamDecl>())
                {
                    // TODO: some real checking here...
                    CheckVarDeclCommon(valParam);
                }
                else if (auto constraint = m.As<GenericTypeConstraintDecl>())
                {
                    CheckGenericConstraintDecl(constraint.Ptr());
                }
            }

            genericDecl->SetCheckState(DeclCheckState::CheckedHeader);
        }

        void visitGenericDecl(GenericDecl* genericDecl)
        {
            checkGenericDeclHeader(genericDecl);

            // check the nested declaration
            // TODO: this needs to be done in an appropriate environment...
            checkDecl(genericDecl->inner);
            genericDecl->SetCheckState(getCheckedState());
        }

        void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl * genericConstraintDecl)
        {
            if (genericConstraintDecl->IsChecked(DeclCheckState::CheckedHeader))
                return;
            // check the type being inherited from
            auto base = genericConstraintDecl->sup;
            base = TranslateTypeNode(base);
            genericConstraintDecl->sup = base;
        }

        void visitInheritanceDecl(InheritanceDecl* inheritanceDecl)
        {
            if (inheritanceDecl->IsChecked(DeclCheckState::CheckedHeader))
                return;
            // check the type being inherited from
            auto base = inheritanceDecl->base;
            CheckConstraintSubType(base);
            base = TranslateTypeNode(base);
            inheritanceDecl->base = base;

            // For now we only allow inheritance from interfaces, so
            // we will validate that the type expression names an interface

            if(auto declRefType = base.type->As<DeclRefType>())
            {
                if(auto interfaceDeclRef = declRefType->declRef.As<InterfaceDecl>())
                {
                    return;
                }
            }

            // If type expression didn't name an interface, we'll emit an error here
            // TODO: deal with the case of an error in the type expression (don't cascade)
            getSink()->diagnose( base.exp, Diagnostics::expectedAnInterfaceGot, base.type);
        }

        RefPtr<ConstantIntVal> checkConstantIntVal(
            RefPtr<Expr>    expr)
        {
            // First type-check the expression as normal
            expr = CheckExpr(expr);

            auto intVal = CheckIntegerConstantExpression(expr.Ptr());
            if(!intVal)
                return nullptr;

            auto constIntVal = intVal.As<ConstantIntVal>();
            if(!constIntVal)
            {
                getSink()->diagnose(expr->loc, Diagnostics::expectedIntegerConstantNotLiteral);
                return nullptr;
            }
            return constIntVal;
        }

        // Check an expression, coerce it to the `String` type, and then
        // ensure that it has a literal (not just compile-time constant) value.
        bool checkLiteralStringVal(
            RefPtr<Expr>    expr,
            String*         outVal)
        {
            // TODO: This should actually perform semantic checking, etc.,
            // but for now we are just going to look for a direct string
            // literal AST node.

            if(auto stringLitExpr = expr.As<StringLiteralExpr>())
            {
                if(outVal)
                {
                    *outVal = stringLitExpr->value;
                }
                return true;
            }

            getSink()->diagnose(expr, Diagnostics::expectedAStringLiteral);

            return false;
        }

        void visitSyntaxDecl(SyntaxDecl*)
        {
            // These are only used in the stdlib, so no checking is needed
        }

        void visitAttributeDecl(AttributeDecl*)
        {
            // These are only used in the stdlib, so no checking is needed
        }

        void visitGenericTypeParamDecl(GenericTypeParamDecl*)
        {
            // These are only used in the stdlib, so no checking is needed for now
        }

        void visitGenericValueParamDecl(GenericValueParamDecl*)
        {
            // These are only used in the stdlib, so no checking is needed for now
        }

        void visitModifier(Modifier*)
        {
            // Do nothing with modifiers for now
        }

        AttributeDecl* lookUpAttributeDecl(Name* attributeName, Scope* scope)
        {
            // Look up the name and see what we find.
            //
            // TODO: This needs to have some special filtering or naming
            // rules to keep us from seeing shadowing variable declarations.
            auto lookupResult = lookUp(getSession(), this, attributeName, scope, LookupMask::Attribute);

            // If we didn't find anything, or the result was overloaded,
            // then we aren't going to be able to extract a single decl.
            if(!lookupResult.isValid() || lookupResult.isOverloaded())
                return nullptr;

            auto decl = lookupResult.item.declRef.getDecl();
            if( auto attributeDecl = dynamic_cast<AttributeDecl*>(decl) )
            {
                return attributeDecl;
            }
            else
            {
                return nullptr;
            }
        }

        bool hasIntArgs(Attribute* attr, int numArgs)
        {
            if (int(attr->args.Count()) != numArgs)
            {
                return false;
            }
            for (int i = 0; i < numArgs; ++i)
            {
                if (!attr->args[i]->As<IntegerLiteralExpr>())
                {
                    return false;
                }
            }
            return true;
        }

        bool hasStringArgs(Attribute* attr, int numArgs)
        {
            if (int(attr->args.Count()) != numArgs)
            {
                return false;
            }
            for (int i = 0; i < numArgs; ++i)
            {
                if (!attr->args[i]->As<StringLiteralExpr>())
                {
                    return false;
                }
            }
            return true;
        }

        bool validateAttribute(RefPtr<Attribute> attr)
        {
                if(auto numThreadsAttr = attr.As<NumThreadsAttribute>())
                {
                    SLANG_ASSERT(attr->args.Count() == 3);
                    auto xVal = checkConstantIntVal(attr->args[0]);
                    auto yVal = checkConstantIntVal(attr->args[1]);
                    auto zVal = checkConstantIntVal(attr->args[2]);

                    if(!xVal) return false;
                    if(!yVal) return false;
                    if(!zVal) return false;

                    numThreadsAttr->x          = (int32_t) xVal->value;
                    numThreadsAttr->y          = (int32_t) yVal->value;
                    numThreadsAttr->z          = (int32_t) zVal->value;
                }
                else if (auto bindingAttr = attr.As<GLSLBindingAttribute>())
                {
                    SLANG_ASSERT(attr->args.Count() == 2);
                    auto binding = checkConstantIntVal(attr->args[0]);
                    auto set = checkConstantIntVal(attr->args[1]);

                    bindingAttr->binding = int32_t(binding->value);
                    bindingAttr->set = int32_t(set->value);
                }
                else if (auto maxVertexCountAttr = attr.As<MaxVertexCountAttribute>())
                {
                    SLANG_ASSERT(attr->args.Count() == 1);
                    auto val = checkConstantIntVal(attr->args[0]);

                    if(!val) return false;

                    maxVertexCountAttr->value = (int32_t)val->value;
                }
                else if(auto instanceAttr = attr.As<InstanceAttribute>())
                {
                    SLANG_ASSERT(attr->args.Count() == 1);
                    auto val = checkConstantIntVal(attr->args[0]);

                    if(!val) return false;

                    instanceAttr->value = (int32_t)val->value;
                }
                else if(auto entryPointAttr = attr.As<EntryPointAttribute>())
                {
                    SLANG_ASSERT(attr->args.Count() == 1);

                    String stageName;
                    if(!checkLiteralStringVal(attr->args[0], &stageName))
                    {
                        return false;
                    }

                    auto stage = findStageByName(stageName);
                    if(stage == Stage::Unknown)
                    {
                        getSink()->diagnose(attr->args[0], Diagnostics::unknownStageName, stageName);
                    }

                    entryPointAttr->stage = stage;
                }
                else if ((attr.As<DomainAttribute>()) ||
                         (attr.As<MaxTessFactorAttribute>()) ||
                         (attr.As<OutputTopologyAttribute>()) ||
                         (attr.As<PartitioningAttribute>()) ||
                         (attr.As<PatchConstantFuncAttribute>()))
                {
                    // Let it go thru iff single string attribute
                    if (!hasStringArgs(attr, 1))
                    {
                        getSink()->diagnose(attr, Diagnostics::expectedSingleStringArg, attr->name);
                    }
                }
                else if (attr.As<OutputControlPointsAttribute>())
                {
                    // Let it go thru iff single integral attribute
                    if (!hasIntArgs(attr, 1))
                    {
                        getSink()->diagnose(attr, Diagnostics::expectedSingleIntArg, attr->name);
                    }
                }
                else if (attr.As<PushConstantAttribute>())
                {
                    // Has no args
                    SLANG_ASSERT(attr->args.Count() == 0);
                }
                else
                {
                    if(attr->args.Count() == 0)
                    {
                        // If the attribute took no arguments, then we will
                        // assume it is valid as written.
                    }
                    else
                    {
                        // We should be special-casing the checking of any attribute
                        // with a non-zero number of arguments.
                        SLANG_DIAGNOSE_UNEXPECTED(getSink(), attr, "unhandled attribute");
                        return false;
                    }
                }

                return true;
        }

        RefPtr<AttributeBase> checkAttribute(
            UncheckedAttribute*     uncheckedAttr,
            ModifiableSyntaxNode*   attrTarget)
        {
            auto attrName = uncheckedAttr->getName();
            auto attrDecl = lookUpAttributeDecl(
                attrName,
                uncheckedAttr->scope);

            if(!attrDecl)
            {
                getSink()->diagnose(uncheckedAttr, Diagnostics::unknownAttributeName, attrName);
                return uncheckedAttr;
            }

            if(!attrDecl->syntaxClass.isSubClassOf<Attribute>())
            {
                SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute declaration does not reference an attribute class");
                return uncheckedAttr;
            }

            RefPtr<RefObject> attrObj = attrDecl->syntaxClass.createInstance();
            auto attr = attrObj.As<Attribute>();
            if(!attr)
            {
                SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute class did not yield an attribute object");
                return uncheckedAttr;
            }

            // We are going to replace the unchecked attribute with the checked one.

            // First copy all of the state over from the original attribute.
            attr->name  = uncheckedAttr->name;
            attr->args  = uncheckedAttr->args;
            attr->loc   = uncheckedAttr->loc;

            // We will start with checking steps that can be applied independent
            // of the concrete attribute type that was selected. These only need
            // us to look at the attribute declaration itself.
            //
            // Start by doing argument/parameter matching
            UInt argCount = attr->args.Count();
            UInt paramCounter = 0;
            bool mismatch = false;
            for(auto paramDecl : attrDecl->getMembersOfType<ParamDecl>())
            {
                UInt paramIndex = paramCounter++;
                if( paramIndex < argCount )
                {
                    auto arg = attr->args[paramIndex];

                    // TODO: support checking the argument against the declared
                    // type for the parameter.

                }
                else
                {
                    // We didn't have enough arguments for the
                    // number of parameters declared.
                    if(auto defaultArg = paramDecl->initExpr)
                    {
                        // The attribute declaration provided a default,
                        // so we should use that.
                        //
                        // TODO: we need to figure out how to hook up
                        // default arguments as needed.
                    }
                    else
                    {
                        mismatch = true;
                    }
                }
            }
            UInt paramCount = paramCounter;

            if(mismatch)
            {
                getSink()->diagnose(attr, Diagnostics::attributeArgumentCountMismatch, attrName, paramCount, argCount);
                return uncheckedAttr;
            }

            // The next bit of validation that we can apply semi-generically
            // is to validate that the target for this attribute is a valid
            // one for the chosen attribute.
            //
            // The attribute declaration will have one or more `AttributeTargetModifier`s
            // that each specify a syntax class that the attribute can be applied to.
            // If any of these match `attrTarget`, then we are good.
            //
            bool validTarget = false;
            for(auto attrTargetMod : attrDecl->GetModifiersOfType<AttributeTargetModifier>())
            {
                if(attrTarget->getClass().isSubClassOf(attrTargetMod->syntaxClass))
                {
                    validTarget = true;
                    break;
                }
            }
            if(!validTarget)
            {
                getSink()->diagnose(attr, Diagnostics::attributeNotApplicable, attrName);
                return uncheckedAttr;
            }

            // Now apply type-specific validation to the attribute.
            if(!validateAttribute(attr))
            {
                return uncheckedAttr;
            }


            return attr;
        }

        RefPtr<Modifier> checkModifier(
            RefPtr<Modifier>        m,
            ModifiableSyntaxNode*   syntaxNode)
        {
            if(auto hlslUncheckedAttribute = m.As<UncheckedAttribute>())
            {
                // We have an HLSL `[name(arg,...)]` attribute, and we'd like
                // to check that it is provides all the expected arguments
                //
                // First, look up the attribute name in the current scope to find
                // the right syntax class to instantiate.
                //

                return checkAttribute(hlslUncheckedAttribute, syntaxNode);
            }
            // Default behavior is to leave things as they are,
            // and assume that modifiers are mostly already checked.
            //
            // TODO: This would be a good place to validate that
            // a modifier is actually valid for the thing it is
            // being applied to, and potentially to check that
            // it isn't in conflict with any other modifiers
            // on the same declaration.

            return m;
        }


        void checkModifiers(ModifiableSyntaxNode* syntaxNode)
        {
            // TODO(tfoley): need to make sure this only
            // performs semantic checks on a `SharedModifier` once...

            // The process of checking a modifier may produce a new modifier in its place,
            // so we will build up a new linked list of modifiers that will replace
            // the old list.
            RefPtr<Modifier> resultModifiers;
            RefPtr<Modifier>* resultModifierLink = &resultModifiers;

            RefPtr<Modifier> modifier = syntaxNode->modifiers.first;
            while(modifier)
            {
                // Because we are rewriting the list in place, we need to extract
                // the next modifier here (not at the end of the loop).
                auto next = modifier->next;

                // We also go ahead and clobber the `next` field on the modifier
                // itself, so that the default behavior of `checkModifier()` can
                // be to return a single unlinked modifier.
                modifier->next = nullptr;

                auto checkedModifier = checkModifier(modifier, syntaxNode);
                if(checkedModifier)
                {
                    // If checking gave us a modifier to add, then we
                    // had better add it.

                    // Just in case `checkModifier` ever returns multiple
                    // modifiers, lets advance to the end of the list we
                    // are building.
                    while(*resultModifierLink)
                        resultModifierLink = &(*resultModifierLink)->next;

                    // attach the new modifier at the end of the list,
                    // and now set the "link" to point to its `next` field
                    *resultModifierLink = checkedModifier;
                    resultModifierLink = &checkedModifier->next;
                }

                // Move along to the next modifier
                modifier = next;
            }

            // Whether we actually re-wrote anything or note, lets
            // install the new list of modifiers on the declaration
            syntaxNode->modifiers.first = resultModifiers;
        }

        void visitModuleDecl(ModuleDecl* programNode)
        {
            // Try to register all the builtin decls
            for (auto decl : programNode->Members)
            {
                auto inner = decl;
                if (auto genericDecl = decl.As<GenericDecl>())
                {
                    inner = genericDecl->inner;
                }

                if (auto builtinMod = inner->FindModifier<BuiltinTypeModifier>())
                {
                    registerBuiltinDecl(getSession(), decl, builtinMod);
                }
                if (auto magicMod = inner->FindModifier<MagicTypeModifier>())
                {
                    registerMagicDecl(getSession(), decl, magicMod);
                }
            }

            // We need/want to visit any `import` declarations before
            // anything else, to make sure that scoping works.
            for(auto& importDecl : programNode->getMembersOfType<ImportDecl>())
            {
                checkDecl(importDecl);
            }
            // register all extensions
            for (auto & s : programNode->getMembersOfType<ExtensionDecl>())
                registerExtension(s);
            for (auto & g : programNode->getMembersOfType<GenericDecl>())
            {
                if (auto extDecl = g->inner->As<ExtensionDecl>())
                {
                    checkGenericDeclHeader(g);
                    registerExtension(extDecl);
                }
            }
            // check types
            for (auto & s : programNode->getMembersOfType<TypeDefDecl>())
                checkDecl(s.Ptr());

            for (int pass = 0; pass < 2; pass++)
            {
                checkingPhase = pass == 0 ? CheckingPhase::Header : CheckingPhase::Body;

                for (auto & s : programNode->getMembersOfType<AggTypeDecl>())
                {
                    checkDecl(s.Ptr());
                }
                // HACK(tfoley): Visiting all generic declarations here,
                // because otherwise they won't get visited.
                for (auto & g : programNode->getMembersOfType<GenericDecl>())
                {
                    checkDecl(g.Ptr());
                }

                // before checking conformance, make sure we check all the extension bodies
                // generic extension decls are already checked by the loop above
                for (auto & s : programNode->getMembersOfType<ExtensionDecl>())
                    checkDecl(s);

                for (auto & func : programNode->getMembersOfType<FuncDecl>())
                {
                    if (!func->IsChecked(getCheckedState()))
                    {
                        VisitFunctionDeclaration(func.Ptr());
                    }
                }
                for (auto & func : programNode->getMembersOfType<FuncDecl>())
                {
                    checkDecl(func);
                }

                if (sink->GetErrorCount() != 0)
                    return;

                // Force everything to be fully checked, just in case
                // Note that we don't just call this on the program,
                // because we'd end up recursing into this very code path...
                for (auto d : programNode->Members)
                {
                    EnusreAllDeclsRec(d);
                }

                if (pass == 0)
                {
                    // now we can check all interface conformances
                    for (auto & s : programNode->getMembersOfType<AggTypeDecl>())
                        checkAggTypeConformance(s);
                    for (auto & s : programNode->getMembersOfType<ExtensionDecl>())
                        checkExtensionConformance(s);
                    for (auto & g : programNode->getMembersOfType<GenericDecl>())
                    {
                        if (auto innerAggDecl = g->inner->As<AggTypeDecl>())
                            checkAggTypeConformance(innerAggDecl);
                        else if (auto innerExtDecl = g->inner->As<ExtensionDecl>())
                            checkExtensionConformance(innerExtDecl);
                    }
                }
            }
        }

        void visitStructField(StructField* field)
        {
            if (field->IsChecked(DeclCheckState::CheckedHeader))
                return;
            // TODO: bottleneck through general-case variable checking

            field->type = CheckUsableType(field->type);
            field->SetCheckState(getCheckedState());
        }

        bool doesSignatureMatchRequirement(
            DeclRef<CallableDecl>   satisfyingMemberDeclRef,
            DeclRef<CallableDecl>   requiredMemberDeclRef,
            RefPtr<WitnessTable>    witnessTable)
        {
            if(satisfyingMemberDeclRef.getDecl()->HasModifier<MutatingAttribute>()
                && !requiredMemberDeclRef.getDecl()->HasModifier<MutatingAttribute>())
            {
                // A `[mutating]` method can't satisfy a non-`[mutating]` requirement,
                // but vice-versa is okay.
                return false;
            }

            if(satisfyingMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()
                != requiredMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>())
            {
                // A `static` method can't satisfy a non-`static` requirement and vice versa.
                return false;
            }

            // TODO: actually implement matching here. For now we'll
            // just pretend that things are satisfied in order to make progress..
            witnessTable->requirementDictionary.Add(
                requiredMemberDeclRef.getDecl(),
                RequirementWitness(satisfyingMemberDeclRef));
            return true;
        }

        bool doesGenericSignatureMatchRequirement(
            DeclRef<GenericDecl>        genDecl,
            DeclRef<GenericDecl>        requirementGenDecl,
            RefPtr<WitnessTable>        witnessTable)
        {
            if (genDecl.getDecl()->Members.Count() != requirementGenDecl.getDecl()->Members.Count())
                return false;
            for (UInt i = 0; i < genDecl.getDecl()->Members.Count(); i++)
            {
                auto genMbr = genDecl.getDecl()->Members[i];
                auto requiredGenMbr = genDecl.getDecl()->Members[i];
                if (auto genTypeMbr = genMbr.As<GenericTypeParamDecl>())
                {
                    if (auto requiredGenTypeMbr = requiredGenMbr.As<GenericTypeParamDecl>())
                    {
                    }
                    else
                        return false;
                }
                else if (auto genValMbr = genMbr.As<GenericValueParamDecl>())
                {
                    if (auto requiredGenValMbr = requiredGenMbr.As<GenericValueParamDecl>())
                    {
                        if (!genValMbr->type->Equals(requiredGenValMbr->type))
                            return false;
                    }
                    else
                        return false;
                }
                else if (auto genTypeConstraintMbr = genMbr.As<GenericTypeConstraintDecl>())
                {
                    if (auto requiredTypeConstraintMbr = requiredGenMbr.As<GenericTypeConstraintDecl>())
                    {
                        if (!genTypeConstraintMbr->sup->Equals(requiredTypeConstraintMbr->sup))
                        {
                            return false;
                        }
                    }
                    else
                        return false;
                }
            }

            // TODO: this isn't right, because we need to specialize the
            // declarations of the generics to a common set of substitutions,
            // so that their types are comparable (e.g., foo<T> and foo<U>
            // need to have substutition applies so that they are both foo<X>,
            // after which uses of the type X in their parameter lists can
            // be compared).

            return doesMemberSatisfyRequirement(
                DeclRef<Decl>(genDecl.getDecl()->inner.Ptr(), genDecl.substitutions),
                DeclRef<Decl>(requirementGenDecl.getDecl()->inner.Ptr(), requirementGenDecl.substitutions),
                witnessTable);
        }

        bool doesTypeSatisfyAssociatedTypeRequirement(
            RefPtr<Type>            satisfyingType,
            DeclRef<AssocTypeDecl>  requiredAssociatedTypeDeclRef,
            RefPtr<WitnessTable>    witnessTable)
        {
            // We need to confirm that the chosen type `satisfyingType`,
            // meets all the constraints placed on the associated type
            // requirement `requiredAssociatedTypeDeclRef`.
            //
            // We will enumerate the type constraints placed on the
            // associated type and see if they can be satisfied.
            //
            bool conformance = true;
            for (auto requiredConstraintDeclRef : getMembersOfType<TypeConstraintDecl>(requiredAssociatedTypeDeclRef))
            {
                // Grab the type we expect to conform to from the constraint.
                auto requiredSuperType = GetSup(requiredConstraintDeclRef);

                // Perform a search for a witness to the subtype relationship.
                auto witness = tryGetSubtypeWitness(satisfyingType, requiredSuperType);
                if(witness)
                {
                    // If a subtype witness was found, then the conformance
                    // appears to hold, and we can satisfy that requirement.
                    witnessTable->requirementDictionary.Add(requiredConstraintDeclRef, RequirementWitness(witness));
                }
                else
                {
                    // If a witness couldn't be found, then the conformance
                    // seems like it will fail.
                    conformance = false;
                }
            }

            // TODO: if any conformance check failed, we should probably include
            // that in an error message produced about not satisfying the requirement.

            if(conformance)
            {
                // If all the constraints were satsified, then the chosen
                // type can indeed satisfy the interface requirement.
                witnessTable->requirementDictionary.Add(
                    requiredAssociatedTypeDeclRef.getDecl(),
                    RequirementWitness(satisfyingType));
            }

            return conformance;
        }

        // Does the given `memberDecl` work as an implementation
        // to satisfy the requirement `requiredMemberDeclRef`
        // from an interface?
        //
        // If it does, then inserts a witness into `witnessTable`
        // and returns `true`, otherwise returns `false`
        bool doesMemberSatisfyRequirement(
            DeclRef<Decl>               memberDeclRef,
            DeclRef<Decl>               requiredMemberDeclRef,
            RefPtr<WitnessTable>        witnessTable)
        {
            // At a high level, we want to check that the
            // `memberDecl` and the `requiredMemberDeclRef`
            // have the same AST node class, and then also
            // check that their signatures match.
            //
            // There are a bunch of detailed decisions that
            // have to be made, though, because we might, e.g.,
            // allow a function with more general parameter
            // types to satisfy a requirement with more
            // specific parameter types.
            //
            // If we ever allow for "property" declarations,
            // then we would probably need to allow an
            // ordinary field to satisfy a property requirement.
            //
            // An associated type requirement should be allowed
            // to be satisfied by any type declaration:
            // a typedef, a `struct`, etc.
            //
            if (auto memberFuncDecl = memberDeclRef.As<FuncDecl>())
            {
                if (auto requiredFuncDeclRef = requiredMemberDeclRef.As<FuncDecl>())
                {
                    // Check signature match.
                    return doesSignatureMatchRequirement(
                        memberFuncDecl,
                        requiredFuncDeclRef,
                        witnessTable);
                }
            }
            else if (auto memberInitDecl = memberDeclRef.As<ConstructorDecl>())
            {
                if (auto requiredInitDecl = requiredMemberDeclRef.As<ConstructorDecl>())
                {
                    // Check signature match.
                    return doesSignatureMatchRequirement(
                        memberInitDecl,
                        requiredInitDecl,
                        witnessTable);
                }
            }
            else if (auto genDecl = memberDeclRef.As<GenericDecl>())
            {
                // For a generic member, we will check if it can satisfy
                // a generic requirement in the interface.
                //
                // TODO: we could also conceivably check that the generic
                // could be *specialized* to satisfy the requirement,
                // and then install a specialization of the generic into
                // the witness table. Actually doing this would seem
                // to require performing something akin to overload
                // resolution as part of requirement satisfaction.
                //
                if (auto requiredGenDeclRef = requiredMemberDeclRef.As<GenericDecl>())
                {
                    return doesGenericSignatureMatchRequirement(genDecl, requiredGenDeclRef, witnessTable);
                }
            }
            else if (auto subAggTypeDeclRef = memberDeclRef.As<AggTypeDecl>())
            {
                if(auto requiredTypeDeclRef = requiredMemberDeclRef.As<AssocTypeDecl>())
                {
                    checkDecl(subAggTypeDeclRef.getDecl());

                    auto satisfyingType = DeclRefType::Create(getSession(), subAggTypeDeclRef);
                    return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable);
                }
            }
            else if (auto typedefDeclRef = memberDeclRef.As<TypeDefDecl>())
            {
                // this is a type-def decl in an aggregate type
                // check if the specified type satisfies the constraints defined by the associated type
                if (auto requiredTypeDeclRef = requiredMemberDeclRef.As<AssocTypeDecl>())
                {
                    checkDecl(typedefDeclRef.getDecl());

                    auto satisfyingType = getNamedType(getSession(), typedefDeclRef);
                    return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable);
                }
            }
            // Default: just assume that thing aren't being satisfied.
            return false;
        }

        // State used while checking if a declaration (either a type declaration
        // or an extension of that type) conforms to the interfaces it claims
        // via its inheritance clauses.
        //
        struct ConformanceCheckingContext
        {
            Dictionary<DeclRef<InterfaceDecl>, RefPtr<WitnessTable>>    mapInterfaceToWitnessTable;
        };

        // Find the appropriate member of a declared type to
        // satisfy a requirement of an interface the type
        // claims to conform to.
        //
        // The type declaration `typeDecl` has declared that it
        // conforms to the interface `interfaceDeclRef`, and
        // `requiredMemberDeclRef` is a required member of
        // the interface.
        //
        // If a satisfying value is found, registers it in
        // `witnessTable` and returns `true`, otherwise
        // returns `false`.
        //
        bool findWitnessForInterfaceRequirement(
            ConformanceCheckingContext* context,
            DeclRef<AggTypeDeclBase>    typeDeclRef,
            InheritanceDecl*            inheritanceDecl,
            DeclRef<InterfaceDecl>      interfaceDeclRef,
            DeclRef<Decl>               requiredMemberDeclRef,
            RefPtr<WitnessTable>        witnessTable)
        {
            // The goal of this function is to find a suitable
            // value to satisfy the requirement.
            //
            // The 99% case is that the requirement is a named member
            // of the interface, and we need to search for a member
            // with the same name in the type declaration and
            // its (known) extensions.

            // As a first pass, lets check if we already have a
            // witness in the table for the requirement, so
            // that we can bail out early.
            //
            if(witnessTable->requirementDictionary.ContainsKey(requiredMemberDeclRef.getDecl()))
            {
                return true;
            }


            // An important exception to the above is that an
            // inheritance declaration in the interface is not going
            // to be satisfied by an inheritance declaration in the
            // conforming type, but rather by a full "witness table"
            // full of the satisfying values for each requirement
            // in the inherited-from interface.
            //
            if( auto requiredInheritanceDeclRef = requiredMemberDeclRef.As<InheritanceDecl>() )
            {
                // Recursively check that the type conforms
                // to the inherited interface.
                //
                // TODO: we *really* need a linearization step here!!!!

                RefPtr<WitnessTable> satisfyingWitnessTable = checkConformanceToType(
                    context,
                    typeDeclRef,
                    requiredInheritanceDeclRef.getDecl(),
                    getBaseType(requiredInheritanceDeclRef));

                if(!satisfyingWitnessTable)
                    return false;

                witnessTable->requirementDictionary.Add(
                    requiredInheritanceDeclRef.getDecl(),
                    RequirementWitness(satisfyingWitnessTable));
                return true;
            }

            // We will look up members with the same name,
            // since only same-name members will be able to
            // satisfy the requirement.
            //
            // TODO: this won't work right now for members that
            // don't have names, which right now includes
            // initializers/constructors.
            Name* name = requiredMemberDeclRef.GetName();

            // We are basically looking up members of the
            // given type, but we need to be a bit careful.
            // We *cannot* perfom lookup "through" inheritance
            // declarations for this or other interfaces,
            // since that would let us satisfy a requirement
            // with itself.
            //
            // There's also an interesting question of whether
            // we can/should support innterface requirements
            // being satisfied via `__transparent` members.
            // This seems like a "clever" idea rather than
            // a useful one, and IR generation would
            // need to construct real IR to trampoline over
            // to the implementation.
            //
            // The final case that can't be reduced to just
            // "a directly declared member with the same name"
            // is the case where the type inherits a member
            // that can satisfy the requirement from a base type.
            // We are ignoring implementation inheritance for
            // now, so we won't worry about this.

            // Make sure that by-name lookup is possible.
            buildMemberDictionary(typeDeclRef.getDecl());
            auto lookupResult = lookUpLocal(getSession(), this, name, typeDeclRef);

            if (!lookupResult.isValid())
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef);
                return false;
            }

            // Iterate over the members and look for one that matches
            // the expected signature for the requirement.
            for (auto member : lookupResult)
            {
                if (doesMemberSatisfyRequirement(member.declRef, requiredMemberDeclRef, witnessTable))
                    return true;
            }

            // No suitable member found, although there were candidates.
            //
            // TODO: Eventually we might want something akin to the current
            // overload resolution logic, where we keep track of a list
            // of "candidates" for satisfaction of the requirement,
            // and if nothing is found we print the candidates

            getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef);
            return false;
        }

        // Check that the type declaration `typeDecl`, which
        // declares conformance to the interface `interfaceDeclRef`,
        // (via the given `inheritanceDecl`) actually provides
        // members to satisfy all the requirements in the interface.
        RefPtr<WitnessTable> checkInterfaceConformance(
            ConformanceCheckingContext* context,
            DeclRef<AggTypeDeclBase>    typeDeclRef,
            InheritanceDecl*            inheritanceDecl,
            DeclRef<InterfaceDecl>      interfaceDeclRef)
        {
            // Has somebody already checked this conformance,
            // and/or is in the middle of checking it?
            RefPtr<WitnessTable> witnessTable;
            if(context->mapInterfaceToWitnessTable.TryGetValue(interfaceDeclRef, witnessTable))
                return witnessTable;

            // We need to check the declaration of the interface
            // before we can check that we conform to it.
            checkDecl(interfaceDeclRef.getDecl());

            // We will construct the witness table, and register it
            // *before* we go about checking fine-grained requirements,
            // in order to short-circuit any potential for infinite recursion.

            // Note: we will re-use the witnes table attached to the inheritance decl,
            // if there is one. This catches cases where semantic checking might
            // have synthesized some of the conformance witnesses for us.
            //
            witnessTable = inheritanceDecl->witnessTable;
            if(!witnessTable)
            {
                witnessTable = new WitnessTable();
            }
            context->mapInterfaceToWitnessTable.Add(interfaceDeclRef, witnessTable);

            bool result = true;

            // TODO: If we ever allow for implementation inheritance,
            // then we will need to consider the case where a type
            // declares that it conforms to an interface, but one of
            // its (non-interface) base types already conforms to
            // that interface, so that all of the requirements are
            // already satisfied with inherited implementations...
            for(auto requiredMemberDeclRef : getMembers(interfaceDeclRef))
            {
                auto requirementSatisfied = findWitnessForInterfaceRequirement(
                    context,
                    typeDeclRef,
                    inheritanceDecl,
                    interfaceDeclRef,
                    requiredMemberDeclRef,
                    witnessTable);

                result = result && requirementSatisfied;
            }

            // Extensions that apply to the interface type can create new conformances
            // for the concrete types that inherit from the interface.
            //
            // These new conformances should not be able to introduce new *requirements*
            // for an implementing interface (although they currently can), but we
            // still need to go through this logic to find the appropriate value
            // that will satisfy the requirement in these cases, and also to put
            // the required entry into the witness table for the interface itself.
            //
            // TODO: This logic is a bit slippery, and we need to figure out what
            // it means in the context of separate compilation. If module A defines
            // an interface IA, module B defines a type C that conforms to IA, and then
            // module C defines an extension that makes IA conform to IC, then it is
            // unreasonable to expect the {B:IA} witness table to contain an entry
            // corresponding to {IA:IC}.
            //
            // The simple answer then would be that the {IA:IC} conformance should be
            // fixed, with a single witness table for {IA:IC}, but then what should
            // happen in B explicitly conformed to IC already?
            //
            // For now we will just walk through the extensions that are known at
            // the time we are compiling and handle those, and punt on the larger issue
            // for abit longer.
            for(auto candidateExt = interfaceDeclRef.getDecl()->candidateExtensions; candidateExt; candidateExt = candidateExt->nextCandidateExtension)
            {
                // We need to apply the extension to the interface type that our
                // concrete type is inheriting from.
                //
                // TODO: need to decide if a this-type substitution is needed here.
                // It probably it.
                RefPtr<Type> targetType = DeclRefType::Create(
                    getSession(),
                    interfaceDeclRef);
                auto extDeclRef = ApplyExtensionToType(candidateExt, targetType);
                if(!extDeclRef)
                    continue;

                // Only inheritance clauses from the extension matter right now.
                for(auto requiredInheritanceDeclRef : getMembersOfType<InheritanceDecl>(extDeclRef))
                {
                    auto requirementSatisfied = findWitnessForInterfaceRequirement(
                        context,
                        typeDeclRef,
                        inheritanceDecl,
                        interfaceDeclRef,
                        requiredInheritanceDeclRef,
                        witnessTable);

                    result = result && requirementSatisfied;
                }
            }

            // If we failed to satisfy any requirements along the way,
            // then we don't actually want to keep the witness table
            // we've been constructing, because the whole thing was a failure.
            if(!result)
            {
                return nullptr;
            }

            return witnessTable;
        }

        RefPtr<WitnessTable> checkConformanceToType(
            ConformanceCheckingContext* context,
            DeclRef<AggTypeDeclBase>    typeDeclRef,
            InheritanceDecl*            inheritanceDecl,
            Type*                       baseType)
        {
            if (auto baseDeclRefType = baseType->As<DeclRefType>())
            {
                auto baseTypeDeclRef = baseDeclRefType->declRef;
                if (auto baseInterfaceDeclRef = baseTypeDeclRef.As<InterfaceDecl>())
                {
                    // The type is stating that it conforms to an interface.
                    // We need to check that it provides all of the members
                    // required by that interface.
                    return checkInterfaceConformance(
                        context,
                        typeDeclRef,
                        inheritanceDecl,
                        baseInterfaceDeclRef);
                }
            }

            getSink()->diagnose(inheritanceDecl, Diagnostics::unimplemented, "type not supported for inheritance");
            return nullptr;
        }

        // Check that the type (or extension) declaration `declRef`,
        // which declares that it inherits from another type via
        // `inheritanceDecl` actually does what it needs to
        // for that inheritance to be valid.
        bool checkConformance(
            DeclRef<AggTypeDeclBase>    declRef,
            InheritanceDecl*            inheritanceDecl)
        {
            declRef = createDefaultSubstitutionsIfNeeded(getSession(), declRef).As<AggTypeDeclBase>();

            // Don't check conformances for abstract types that
            // are being used to express *required* conformances.
            if (auto assocTypeDeclRef = declRef.As<AssocTypeDecl>())
            {
                // An associated type declaration represents a requirement
                // in an outer interface declaration, and its members
                // (type constraints) represent additional requirements.
                return true;
            }
            else if (auto interfaceDeclRef = declRef.As<InterfaceDecl>())
            {
                // HACK: Our semantics as they stand today are that an
                // `extension` of an interface that adds a new inheritance
                // clause acts *as if* that inheritnace clause had been
                // attached to the original `interface` decl: that is,
                // it adds additional requirements.
                //
                // This is *not* a reasonable semantic to keep long-term,
                // but it is required for some of our current example
                // code to work.
                return true;
            }


            // Look at the type being inherited from, and validate
            // appropriately.
            auto baseType = inheritanceDecl->base.type;

            ConformanceCheckingContext context;
            RefPtr<WitnessTable> witnessTable = checkConformanceToType(&context, declRef, inheritanceDecl, baseType);
            if(!witnessTable)
                return false;

            inheritanceDecl->witnessTable = witnessTable;
            return true;
        }

        void checkExtensionConformance(ExtensionDecl* decl)
        {
            if (auto targetDeclRefType = decl->targetType->As<DeclRefType>())
            {
                if (auto aggTypeDeclRef = targetDeclRefType->declRef.As<AggTypeDecl>())
                {
                    for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
                    {
                        checkConformance(aggTypeDeclRef, inheritanceDecl);
                    }
                }
            }
        }

        void checkAggTypeConformance(AggTypeDecl* decl)
        {
            // After we've checked members, we need to go through
            // any inheritance clauses on the type itself, and
            // confirm that the type actually provides whatever
            // those clauses require.

            if (auto interfaceDecl = dynamic_cast<InterfaceDecl*>(decl))
            {
                // Don't check that an interface conforms to the
                // things it inherits from.
            }
            else if (auto assocTypeDecl = dynamic_cast<AssocTypeDecl*>(decl))
            {
                // Don't check that an associated type decl conforms to the
                // things it inherits from.
            }
            else
            {
                // For non-interface types we need to check conformance.
                //
                // TODO: Need to figure out what this should do for
                // `abstract` types if we ever add them. Should they
                // be required to implement all interface requirements,
                // just with `abstract` methods that replicate things?
                // (That's what C# does).
                for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
                {
                    checkConformance(makeDeclRef(decl), inheritanceDecl);
                }
            }
        }

        void visitAggTypeDecl(AggTypeDecl* decl)
        {
            if (decl->IsChecked(getCheckedState()))
                return;

            // TODO: we should check inheritance declarations
            // first, since they need to be validated before
            // we can make use of the type (e.g., you need
            // to know that `A` inherits from `B` in order
            // to check an expression like `aValue.bMethod()`
            // where `aValue` is of type `A` but `bMethod`
            // is defined in type `B`.
            //
            // TODO: We should also add a pass that takes
            // all the stated inheritance relationships,
            // expands them to include implicitic inheritance,
            // and then linearizes them. This would allow
            // later passes that need to know everything
            // a type inherits from to proceed linearly
            // through the list, rather than having to
            // recurse (and potentially see the same interface
            // more than once).

            decl->SetCheckState(DeclCheckState::CheckedHeader);

            // Now check all of the member declarations.
            for (auto member : decl->Members)
            {
                checkDecl(member);
            }
            decl->SetCheckState(getCheckedState());
        }

        bool isIntegerBaseType(BaseType baseType)
        {
            switch(baseType)
            {
            default:
                return false;

            case BaseType::Int8:
            case BaseType::Int16:
            case BaseType::Int:
            case BaseType::Int64:
            case BaseType::UInt8:
            case BaseType::UInt16:
            case BaseType::UInt:
            case BaseType::UInt64:
                return true;
            }
        }

        // Validate that `type` is a suitable type to use
        // as the tag type for an `enum`
        void validateEnumTagType(Type* type, SourceLoc const& loc)
        {
            if(auto basicType = type->As<BasicExpressionType>())
            {
                // Allow the built-in intteger types.
                if(isIntegerBaseType(basicType->baseType))
                    return;

                // By default, don't allow other types to be used
                // as an `enum` tag type.
            }

            getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type);
        }

        void visitEnumDecl(EnumDecl* decl)
        {
            if (decl->IsChecked(getCheckedState()))
                return;

            // Look at inheritance clauses, and
            // see if one of them is making the enum
            // "inherit" from a concrete type.
            // This will become the "tag" type
            // of the enum.
            RefPtr<Type>        tagType;
            InheritanceDecl*    tagTypeInheritanceDecl = nullptr;
            for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
            {
                checkDecl(inheritanceDecl);

                // Look at the type being inherited from.
                auto superType = inheritanceDecl->base.type;

                if(auto errorType = superType->As<ErrorType>())
                {
                    // Ignore any erroneous inheritance clauses.
                    continue;
                }
                else if(auto declRefType = superType->As<DeclRefType>())
                {
                    if(auto interfaceDeclRef = declRefType->declRef.As<InterfaceDecl>())
                    {
                        // Don't consider interface bases as candidates for
                        // the tag type.
                        continue;
                    }
                }

                if(tagType)
                {
                    // We already found a tag type.
                    getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType);
                    getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType);
                    break;
                }
                else
                {
                    tagType = superType;
                    tagTypeInheritanceDecl = inheritanceDecl;
                }
            }

            // If a tag type has not been set, then we
            // default it to the built-in `int` type.
            //
            // TODO: In the far-flung future we may want to distinguish
            // `enum` types that have a "raw representation" like this from
            // ones that are purely abstract and don't expose their
            // type of their tag.
            if(!tagType)
            {
                tagType = getSession()->getIntType();
            }
            else
            {
                // TODO: Need to establish that the tag
                // type is suitable. (e.g., if we are going
                // to allow raw values for case tags to be
                // derived automatically, then the tag
                // type needs to be some kind of interer type...)
                //
                // For now we will just be harsh and require it
                // to be one of a few builtin types.
                validateEnumTagType(tagType, tagTypeInheritanceDecl->loc);
            }
            decl->tagType = tagType;


            // An `enum` type should automatically conform to the `__EnumType` interface.
            // The compiler needs to insert this conformance behind the scenes, and this
            // seems like the best place to do it.
            {
                // First, look up the type of the `__EnumType` interface.
                RefPtr<Type> enumTypeType = getSession()->getEnumTypeType();

                RefPtr<InheritanceDecl> enumConformanceDecl = new InheritanceDecl();
                enumConformanceDecl->ParentDecl = decl;
                enumConformanceDecl->loc = decl->loc;
                enumConformanceDecl->base.type = getSession()->getEnumTypeType();
                decl->Members.Add(enumConformanceDecl);

                // The `__EnumType` interface has one required member, the `__Tag` type.
                // We need to satisfy this requirement automatically, rather than require
                // the user to actually declare a member with this name (otherwise we wouldn't
                // let them define a tag value with the name `__Tag`).
                //
                RefPtr<WitnessTable> witnessTable = new WitnessTable();
                enumConformanceDecl->witnessTable = witnessTable;

                Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag");
                Decl* tagAssociatedTypeDecl = nullptr;
                if(auto enumTypeTypeDeclRefType = enumTypeType.As<DeclRefType>())
                {
                    if(auto enumTypeTypeInterfaceDecl = enumTypeTypeDeclRefType->declRef.getDecl()->As<InterfaceDecl>())
                    {
                        for(auto memberDecl : enumTypeTypeInterfaceDecl->Members)
                        {
                            if(memberDecl->getName() == tagAssociatedTypeName)
                            {
                                tagAssociatedTypeDecl = memberDecl;
                                break;
                            }
                        }
                    }
                }
                if(!tagAssociatedTypeDecl)
                {
                    SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'");
                }

                // Okay, add the conformance withess for `__Tag` being satisfied by `tagType`
                witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType));

                // TODO: we actually also need to synthesize a witness for the conformance of `tagType`
                // to the `__BuiltinIntegerType` interface, because that is a constraint on the
                // associated type `__Tag`.

                // TODO: eventually we should consider synthesizing other requirements for
                // the min/max tag values, or the total number of tags, so that people don't
                // have to declare these as additional cases.

                enumConformanceDecl->SetCheckState(DeclCheckState::Checked);
            }


            decl->SetCheckState(DeclCheckState::CheckedHeader);

            auto enumType = DeclRefType::Create(
                getSession(),
                makeDeclRef(decl));

            // Check the enum cases in order.
            for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
            {
                // Each case defines a value of the enum's type.
                //
                // TODO: If we ever support enum cases with payloads,
                // then they would probably have a type that is a
                // `FunctionType` from the payload types to the
                // enum type.
                //
                caseDecl->type.type = enumType;

                checkDecl(caseDecl);
            }

            // For any enum case that didn't provide an explicit
            // tag value, derived an appropriate tag value.
            IntegerLiteralValue defaultTag = 0;
            for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
            {
                if(auto explicitTagValExpr = caseDecl->initExpr)
                {
                    // This tag has an initializer, so it should establish
                    // the tag value for a successor case that doesn't
                    // provide an explicit tag.

                    RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr);
                    if(explicitTagVal)
                    {
                        if(auto constIntVal = explicitTagVal.As<ConstantIntVal>())
                        {
                            defaultTag = constIntVal->value;
                        }
                        else
                        {
                            // TODO: need to handle other possibilities here
                            getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr);
                        }
                    }
                    else
                    {
                        // If this happens, then the explicit tag value expression
                        // doesn't seem to be a constant after all. In this case
                        // we expect the checking logic to have applied already.
                    }
                }
                else
                {
                    // This tag has no initializer, so it should use
                    // the default tag value we are tracking.
                    RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr();
                    tagValExpr->loc = caseDecl->loc;
                    tagValExpr->type = QualType(tagType);
                    tagValExpr->value = defaultTag;

                    caseDecl->initExpr = tagValExpr;
                }

                // Default tag for the next case will be one more than
                // for the most recent case.
                //
                // TODO: We might consider adding a `[flags]` attribute
                // that modifies this behavior to be `defaultTagForCase <<= 1`.
                //
                defaultTag++;
            }

            // Now check any other member declarations.
            for(auto memberDecl : decl->Members)
            {
                // Already checked inheritance declarations above.
                if(auto inheritanceDecl = memberDecl->As<InheritanceDecl>())
                    continue;

                // Already checked enum case declarations above.
                if(auto caseDecl = memberDecl->As<EnumCaseDecl>())
                    continue;

                // TODO: Right now we don't support other kinds of
                // member declarations on an `enum`, but that is
                // something we may want to allow in the long run.
                //
                checkDecl(memberDecl);
            }
            decl->SetCheckState(getCheckedState());
        }

        void visitEnumCaseDecl(EnumCaseDecl* decl)
        {
            if (decl->IsChecked(getCheckedState()))
                return;

            // An enum case had better appear inside an enum!
            //
            // TODO: Do we need/want to support generic cases some day?
            auto parentEnumDecl = decl->ParentDecl->As<EnumDecl>();
            SLANG_ASSERT(parentEnumDecl);

            // The tag type should have already been set by
            // the surrounding `enum` declaration.
            auto tagType = parentEnumDecl->tagType;
            SLANG_ASSERT(tagType);

            // Need to check the init expression, if present, since
            // that represents the explicit tag for this case.
            if(auto initExpr = decl->initExpr)
            {
                initExpr = CheckExpr(initExpr);
                initExpr = Coerce(tagType, initExpr);

                // We want to enforce that this is an integer constant
                // expression, but we don't actually care to retain
                // the value.
                CheckIntegerConstantExpression(initExpr);

                decl->initExpr = initExpr;
            }
            decl->SetCheckState(getCheckedState());
        }

        void visitDeclGroup(DeclGroup* declGroup)
        {
            for (auto decl : declGroup->decls)
            {
                dispatchDecl(decl);
            }
        }

        void visitTypeDefDecl(TypeDefDecl* decl)
        {
            if (decl->IsChecked(getCheckedState())) return;
            if (checkingPhase == CheckingPhase::Header)
            {
                decl->type = CheckProperType(decl->type);
            }
            decl->SetCheckState(getCheckedState());
        }

        void visitGlobalGenericParamDecl(GlobalGenericParamDecl * decl)
        {
            if (decl->IsChecked(getCheckedState())) return;
            if (checkingPhase == CheckingPhase::Header)
            {
                decl->SetCheckState(DeclCheckState::CheckedHeader);
                // global generic param only allowed in global scope
                auto program = decl->ParentDecl->As<ModuleDecl>();
                if (!program)
                    getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly);
                // Now check all of the member declarations.
                for (auto member : decl->Members)
                {
                    checkDecl(member);
                }
            }
            decl->SetCheckState(getCheckedState());
        }

        void visitAssocTypeDecl(AssocTypeDecl* decl)
        {
            if (decl->IsChecked(getCheckedState())) return;
            if (checkingPhase == CheckingPhase::Header)
            {
                decl->SetCheckState(DeclCheckState::CheckedHeader);

                // assoctype only allowed in an interface
                auto interfaceDecl = decl->ParentDecl->As<InterfaceDecl>();
                if (!interfaceDecl)
                    getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly);

                // Now check all of the member declarations.
                for (auto member : decl->Members)
                {
                    checkDecl(member);
                }
            }
            decl->SetCheckState(getCheckedState());
        }

        void checkStmt(Stmt* stmt)
        {
            if (!stmt) return;
            dispatchStmt(stmt);
            checkModifiers(stmt);
        }

        void visitFuncDecl(FuncDecl *functionNode)
        {
            if (functionNode->IsChecked(getCheckedState()))
                return;

            if (checkingPhase == CheckingPhase::Header)
            {
                VisitFunctionDeclaration(functionNode);
            }
            // TODO: This should really only set "checked header"
            functionNode->SetCheckState(getCheckedState());

            if (checkingPhase == CheckingPhase::Body)
            {
                // TODO: should put the checking of the body onto a "work list"
                // to avoid recursion here.
                if (functionNode->Body)
                {
                    auto oldFunc = function;
                    this->function = functionNode;
                    checkStmt(functionNode->Body);
                    this->function = oldFunc;
                }
            }
        }

        void getGenericParams(
            GenericDecl*                        decl,
            List<Decl*>&                        outParams,
            List<GenericTypeConstraintDecl*>    outConstraints)
        {
            for (auto dd : decl->Members)
            {
                if (dd == decl->inner)
                    continue;

                if (auto typeParamDecl = dd.As<GenericTypeParamDecl>())
                    outParams.Add(typeParamDecl);
                else if (auto valueParamDecl = dd.As<GenericValueParamDecl>())
                    outParams.Add(valueParamDecl);
                else if (auto constraintDecl = dd.As<GenericTypeConstraintDecl>())
                    outConstraints.Add(constraintDecl);
            }
        }

        bool doGenericSignaturesMatch(
            GenericDecl*    fst,
            GenericDecl*    snd)
        {
            // First we'll extract the parameters and constraints
            // in each generic signature. We will consider parameters
            // and constraints separately so that we are independent
            // of the order in which constraints are given (that is,
            // a constraint like `<T : IFoo>` whould be considered
            // the same as `<T>` with a later `where T : IFoo`.

            List<Decl*> fstParams;
            List<GenericTypeConstraintDecl*> fstConstraints;
            getGenericParams(fst, fstParams, fstConstraints);

            List<Decl*> sndParams;
            List<GenericTypeConstraintDecl*> sndConstraints;
            getGenericParams(snd, sndParams, sndConstraints);

            // For there to be any hope of a match, the
            // two need to have the same number of parameters.
            UInt paramCount = fstParams.Count();
            if (paramCount != sndParams.Count())
                return false;

            // Now we'll walk through the parameters.
            for (UInt pp = 0; pp < paramCount; ++pp)
            {
                Decl* fstParam = fstParams[pp];
                Decl* sndParam = sndParams[pp];

                if (auto fstTypeParam = dynamic_cast<GenericTypeParamDecl*>(fstParam))
                {
                    if (auto sndTypeParam = dynamic_cast<GenericTypeParamDecl*>(sndParam))
                    {
                        // TODO: is there any validation that needs to be peformed here?
                    }
                    else
                    {
                        // Type and non-type parameters can't match.
                        return false;
                    }
                }
                else if (auto fstValueParam = dynamic_cast<GenericValueParamDecl*>(fstParam))
                {
                    if (auto sndValueParam = dynamic_cast<GenericValueParamDecl*>(sndParam))
                    {
                        // Need to check that the parameters have the same type.
                        //
                        // Note: We are assuming here that the type of a value
                        // parameter cannot be dependent on any of the type
                        // parameters in the same signature. This is a reasonable
                        // assumption for now, but could get thorny down the road.
                        if (!fstValueParam->getType()->Equals(sndValueParam->getType()))
                        {
                            // Type mismatch.
                            return false;
                        }

                        // TODO: This is not the right place to check on default
                        // values for the parameter, because they won't affect
                        // the signature, but we should make sure to do validation
                        // later on (e.g., that only one declaration can/should
                        // be allowed to provide a default).
                    }
                    else
                    {
                        // Value and non-value parameters can't match.
                        return false;
                    }
                }
            }

            // If we got this far, then it means the parameter signatures *seem*
            // to match up all right, but now we need to check that the constraints
            // placed on those parameters are also consistent.
            //
            // For now I'm going to assume/require that all declarations must
            // declare the signature in a way that matches exactly.
            UInt constraintCount = fstConstraints.Count();
            if(constraintCount != sndConstraints.Count())
                return false;

            for (UInt cc = 0; cc < constraintCount; ++cc)
            {
                //auto fstConstraint = fstConstraints[cc];
                //auto sndConstraint = sndConstraints[cc];

                // TODO: the challenge here is that the
                // constraints are going to be expressed
                // in terms of the parameters, which means
                // we need to be doing substitution here.
            }

            // HACK: okay, we'll just assume things match for now.
            return true;
        }

        // Check if two functions have the same signature for the purposes
        // of overload resolution.
        bool doFunctionSignaturesMatch(
            DeclRef<FuncDecl> fst,
            DeclRef<FuncDecl> snd)
        {

            // TODO(tfoley): This copies the parameter array, which is bad for performance.
            auto fstParams = GetParameters(fst).ToArray();
            auto sndParams = GetParameters(snd).ToArray();

            // If the functions have different numbers of parameters, then
            // their signatures trivially don't match.
            auto fstParamCount = fstParams.Count();
            auto sndParamCount = sndParams.Count();
            if (fstParamCount != sndParamCount)
                return false;

            for (UInt ii = 0; ii < fstParamCount; ++ii)
            {
                auto fstParam = fstParams[ii];
                auto sndParam = sndParams[ii];

                // If a given parameter type doesn't match, then signatures don't match
                if (!GetType(fstParam)->Equals(GetType(sndParam)))
                    return false;

                // If one parameter is `out` and the other isn't, then they don't match
                //
                // Note(tfoley): we don't consider `out` and `inout` as distinct here,
                // because there is no way for overload resolution to pick between them.
                if (fstParam.getDecl()->HasModifier<OutModifier>() != sndParam.getDecl()->HasModifier<OutModifier>())
                    return false;

                // If one parameter is `ref` and the other isn't, then they don't match.
                //
                if(fstParam.getDecl()->HasModifier<RefModifier>() != sndParam.getDecl()->HasModifier<RefModifier>())
                    return false;
            }

            // Note(tfoley): return type doesn't enter into it, because we can't take
            // calling context into account during overload resolution.

            return true;
        }

        RefPtr<GenericSubstitution> createDummySubstitutions(
            GenericDecl* genericDecl)
        {
            RefPtr<GenericSubstitution> subst = new GenericSubstitution();
            subst->genericDecl = genericDecl;
            for (auto dd : genericDecl->Members)
            {
                if (dd == genericDecl->inner)
                    continue;

                if (auto typeParam = dd.As<GenericTypeParamDecl>())
                {
                    auto type = DeclRefType::Create(getSession(),
                        makeDeclRef(typeParam.Ptr()));
                    subst->args.Add(type);
                }
                else if (auto valueParam = dd.As<GenericValueParamDecl>())
                {
                    auto val = new GenericParamIntVal(
                        makeDeclRef(valueParam.Ptr()));
                    subst->args.Add(val);
                }
                // TODO: need to handle constraints here?
            }
            return subst;
        }

        void ValidateFunctionRedeclaration(FuncDecl* funcDecl)
        {
            auto parentDecl = funcDecl->ParentDecl;
            SLANG_ASSERT(parentDecl);
            if (!parentDecl) return;

            Decl* childDecl = funcDecl;

            // If this is a generic function (that is, its parent
            // declaration is a generic), then we need to look
            // for sibling declarations of the parent.
            auto genericDecl = dynamic_cast<GenericDecl*>(parentDecl);
            if (genericDecl)
            {
                parentDecl = genericDecl->ParentDecl;
                childDecl = genericDecl;
            }

            // Look at previously-declared functions with the same name,
            // in the same container
            //
            // Note: there is an assumption here that declarations that
            // occur earlier in the program  text will be *later* in
            // the linked list of declarations with the same name.
            // We are also assuming/requiring that the check here is
            // symmetric, in that it is okay to test (A,B) or (B,A),
            // and there is no need to test both.
            //
            buildMemberDictionary(parentDecl);
            for (auto pp = childDecl->nextInContainerWithSameName; pp; pp = pp->nextInContainerWithSameName)
            {
                auto prevDecl = pp;

                // Look through generics to the declaration underneath
                auto prevGenericDecl = dynamic_cast<GenericDecl*>(prevDecl);
                if (prevGenericDecl)
                    prevDecl = prevGenericDecl->inner.Ptr();

                // We only care about previously-declared functions
                // Note(tfoley): although we should really error out if the
                // name is already in use for something else, like a variable...
                auto prevFuncDecl = dynamic_cast<FuncDecl*>(prevDecl);
                if (!prevFuncDecl)
                    continue;

                // If one declaration is a prefix/postfix operator, and the
                // other is not a matching operator, then don't consider these
                // to be redeclarations.
                //
                // Note(tfoley): Any attempt to call such an operator using
                // ordinary function-call syntax (if we decided to allow it)
                // would be ambiguous in such a case, of course.
                //
                if (funcDecl->HasModifier<PrefixModifier>() != prevDecl->HasModifier<PrefixModifier>())
                    continue;
                if (funcDecl->HasModifier<PostfixModifier>() != prevDecl->HasModifier<PostfixModifier>())
                    continue;

                // If one is generic and the other isn't, then there is no match.
                if ((genericDecl != nullptr) != (prevGenericDecl != nullptr))
                    continue;

                // We are going to be comparing the signatures of the
                // two functions, but if they are *generic* functions
                // then we will need to compare them with consistent
                // specializations in place.
                //
                // We'll go ahead and create some (unspecialized) declaration
                // references here, just to be prepared.
                DeclRef<FuncDecl> funcDeclRef(funcDecl, nullptr);
                DeclRef<FuncDecl> prevFuncDeclRef(prevFuncDecl, nullptr);

                // If we are working with generic functions, then we need to
                // consider if their generic signatures match.
                if (genericDecl)
                {
                    SLANG_ASSERT(prevGenericDecl); // already checked above
                    if (!doGenericSignaturesMatch(genericDecl, prevGenericDecl))
                        continue;

                    // Now we need specialize the declaration references
                    // consistently, so that we can compare.
                    //
                    // First we create a "dummy" set of substitutions that
                    // just reference the parameters of the first generic.
                    auto subst = createDummySubstitutions(genericDecl);
                    //
                    // Then we use those parameters to specialize the *other*
                    // generic.
                    //
                    subst->genericDecl = prevGenericDecl;
                    prevFuncDeclRef.substitutions.substitutions = subst;
                    //
                    // One way to think about it is that if we have these
                    // declarations (ignore the name differences...):
                    //
                    //     // prevFuncDecl:
                    //     void foo1<T>(T x);
                    //
                    //     // funcDecl:
                    //     void foo2<U>(U x);
                    //
                    // Then we will compare `foo2` against `foo1<U>`.
                }

                // If the parameter signatures don't match, then don't worry
                if (!doFunctionSignaturesMatch(funcDeclRef, prevFuncDeclRef))
                    continue;

                // If we get this far, then we've got two declarations in the same
                // scope, with the same name and signature, so they appear
                // to be redeclarations.
                //
                // We will track that redeclaration occured, so that we can
                // take it into account for overload resolution.
                //
                // A huge complication that we'll need to deal with is that
                // multiple declarations might introduce default values for
                // (different) parameters, and we might need to merge across
                // all of them (which could get complicated if defaults for
                // parameters can reference earlier parameters).

                // If the previous declaration wasn't already recorded
                // as being part of a redeclaration family, then make
                // it the primary declaration of a new family.
                if (!prevFuncDecl->primaryDecl)
                {
                    prevFuncDecl->primaryDecl = prevFuncDecl;
                }

                // The new declaration will belong to the family of
                // the previous one, and so it will share the same
                // primary declaration.
                funcDecl->primaryDecl = prevFuncDecl->primaryDecl;
                funcDecl->nextDecl = nullptr;

                // Next we want to chain the new declaration onto
                // the linked list of redeclarations.
                auto link = &prevFuncDecl->nextDecl;
                while (*link)
                    link = &(*link)->nextDecl;
                *link = funcDecl;

                // Now that we've added things to a group of redeclarations,
                // we can do some additional validation.

                // First, we will ensure that the return types match
                // between the declarations, so that they are truly
                // interchangeable.
                //
                // Note(tfoley): If we ever decide to add a beefier type
                // system to Slang, we might allow overloads like this,
                // so long as the desired result type can be disambiguated
                // based on context at the call type. In that case we would
                // consider result types earlier, as part of the signature
                // matching step.
                //
                auto resultType = GetResultType(funcDeclRef);
                auto prevResultType = GetResultType(prevFuncDeclRef);
                if (!resultType->Equals(prevResultType))
                {
                    // Bad redeclaration
                    getSink()->diagnose(funcDecl, Diagnostics::functionRedeclarationWithDifferentReturnType, funcDecl->getName(), resultType, prevResultType);
                    getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDeclarationOf, funcDecl->getName());

                    // Don't bother emitting other errors at this point
                    break;
                }

                // Note(tfoley): several of the following checks should
                // really be looping over all the previous declarations
                // in the same group, and not just the one previous
                // declaration we found just now.

                // TODO: Enforce that the new declaration had better
                // not specify a default value for any parameter that
                // already had a default value in a prior declaration.

                // We are going to want to enforce that we cannot have
                // two declarations of a function both specify bodies.
                // Before we make that check, however, we need to deal
                // with the case where the two function declarations
                // might represent different target-specific versions
                // of a function.
                //
                // TODO: if the two declarations are specialized for
                // different targets, then skip the body checks below.

                // If both of the declarations have a body, then there
                // is trouble, because we wouldn't know which one to
                // use during code generation.
                if (funcDecl->Body && prevFuncDecl->Body)
                {
                    // Redefinition
                    getSink()->diagnose(funcDecl, Diagnostics::functionRedefinition, funcDecl->getName());
                    getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDefinitionOf, funcDecl->getName());

                    // Don't bother emitting other errors
                    break;
                }

                // At this point we've processed the redeclaration and
                // put it into a group, so there is no reason to keep
                // looping and looking at prior declarations.
                return;
            }
        }

        void visitScopeDecl(ScopeDecl*)
        {
            // Nothing to do
        }

        void visitParamDecl(ParamDecl* paramDecl)
        {
            // TODO: This logic should be shared with the other cases of
            // variable declarations. The main reason I am not doing it
            // yet is that we use a `ParamDecl` with a null type as a
            // special case in attribute declarations, and that could
            // trip up the ordinary variable checks.

            auto typeExpr = paramDecl->type;
            if(typeExpr.exp)
            {
                typeExpr = CheckUsableType(typeExpr);
                paramDecl->type = typeExpr;
            }

            paramDecl->SetCheckState(DeclCheckState::CheckedHeader);

            // The "initializer" expression for a parameter represents
            // a default argument value to use if an explicit one is
            // not supplied.
            if(auto initExpr = paramDecl->initExpr)
            {
                // We must check the expression and coerce it to the
                // actual type of the parameter.
                //
                initExpr = CheckExpr(initExpr);
                initExpr = Coerce(typeExpr.type, initExpr);
                paramDecl->initExpr = initExpr;

                // TODO: a default argument expression needs to
                // conform to other constraints to be valid.
                // For example, it should not be allowed to refer
                // to other parameters of the same function (or maybe
                // only the parameters to its left...).
            }

            paramDecl->SetCheckState(DeclCheckState::Checked);
        }

        void VisitFunctionDeclaration(FuncDecl *functionNode)
        {
            if (functionNode->IsChecked(DeclCheckState::CheckedHeader)) return;
            functionNode->SetCheckState(DeclCheckState::CheckingHeader);
            auto oldFunc = this->function;
            this->function = functionNode;
            auto returnType = CheckProperType(functionNode->ReturnType);
            functionNode->ReturnType = returnType;
            HashSet<Name*> paraNames;
            for (auto & para : functionNode->GetParameters())
            {
                EnsureDecl(para, DeclCheckState::CheckedHeader);

                if (paraNames.Contains(para->getName()))
                {
                    getSink()->diagnose(para, Diagnostics::parameterAlreadyDefined, para->getName());
                }
                else
                    paraNames.Add(para->getName());
            }
            this->function = oldFunc;
            functionNode->SetCheckState(DeclCheckState::CheckedHeader);

            // One last bit of validation: check if we are redeclaring an existing function
            ValidateFunctionRedeclaration(functionNode);
        }

        void visitDeclStmt(DeclStmt* stmt)
        {
            // We directly dispatch here instead of using `EnsureDecl()` for two
            // reasons:
            //
            // 1. We expect that a local declaration won't have been referenced
            // before it is declared, so that we can just check things in-order
            //
            // 2. `EnsureDecl()` is specialized for `Decl*` instead of `DeclBase*`
            // and trying to special case `DeclGroup*` here feels silly.
            //
            dispatchDecl(stmt->decl);
            checkModifiers(stmt->decl);
        }

        void visitBlockStmt(BlockStmt* stmt)
        {
            checkStmt(stmt->body);
        }

        void visitSeqStmt(SeqStmt* stmt)
        {
            for(auto ss : stmt->stmts)
            {
                checkStmt(ss);
            }
        }

        template<typename T>
        T* FindOuterStmt()
        {
            UInt outerStmtCount = outerStmts.Count();
            for (UInt ii = outerStmtCount; ii > 0; --ii)
            {
                auto outerStmt = outerStmts[ii-1];
                auto found = dynamic_cast<T*>(outerStmt);
                if (found)
                    return found;
            }
            return nullptr;
        }

        void visitBreakStmt(BreakStmt *stmt)
        {
            auto outer = FindOuterStmt<BreakableStmt>();
            if (!outer)
            {
                getSink()->diagnose(stmt, Diagnostics::breakOutsideLoop);
            }
            stmt->parentStmt = outer;
        }
        void visitContinueStmt(ContinueStmt *stmt)
        {
            auto outer = FindOuterStmt<LoopStmt>();
            if (!outer)
            {
                getSink()->diagnose(stmt, Diagnostics::continueOutsideLoop);
            }
            stmt->parentStmt = outer;
        }

        void PushOuterStmt(Stmt* stmt)
        {
            outerStmts.Add(stmt);
        }

        void PopOuterStmt(Stmt* /*stmt*/)
        {
            outerStmts.RemoveAt(outerStmts.Count() - 1);
        }

        RefPtr<Expr> checkPredicateExpr(Expr* expr)
        {
            RefPtr<Expr> e = expr;
            e = CheckTerm(e);
            e = Coerce(getSession()->getBoolType(), e);
            return e;
        }

        void visitDoWhileStmt(DoWhileStmt *stmt)
        {
            PushOuterStmt(stmt);
            stmt->Predicate = checkPredicateExpr(stmt->Predicate);
            checkStmt(stmt->Statement);

            PopOuterStmt(stmt);
        }
        void visitForStmt(ForStmt *stmt)
        {
            PushOuterStmt(stmt);
            checkStmt(stmt->InitialStatement);
            if (stmt->PredicateExpression)
            {
                stmt->PredicateExpression = checkPredicateExpr(stmt->PredicateExpression);
            }
            if (stmt->SideEffectExpression)
            {
                stmt->SideEffectExpression = CheckExpr(stmt->SideEffectExpression);
            }
            checkStmt(stmt->Statement);

            PopOuterStmt(stmt);
        }

        RefPtr<Expr> checkExpressionAndExpectIntegerConstant(RefPtr<Expr> expr, RefPtr<IntVal>* outIntVal)
        {
            expr = CheckExpr(expr);
            auto intVal = CheckIntegerConstantExpression(expr);
            if (outIntVal)
                *outIntVal = intVal;
            return expr;
        }

        void visitCompileTimeForStmt(CompileTimeForStmt* stmt)
        {
            PushOuterStmt(stmt);

            stmt->varDecl->type.type = getSession()->getIntType();
            addModifier(stmt->varDecl, new ConstModifier());
            stmt->varDecl->SetCheckState(DeclCheckState::Checked);

            RefPtr<IntVal> rangeBeginVal;
            RefPtr<IntVal> rangeEndVal;

            if (stmt->rangeBeginExpr)
            {
                stmt->rangeBeginExpr = checkExpressionAndExpectIntegerConstant(stmt->rangeBeginExpr, &rangeBeginVal);
            }
            else
            {
                RefPtr<ConstantIntVal> rangeBeginConst = new ConstantIntVal();
                rangeBeginConst->value = 0;
                rangeBeginVal = rangeBeginConst;
            }

            stmt->rangeEndExpr = checkExpressionAndExpectIntegerConstant(stmt->rangeEndExpr, &rangeEndVal);

            stmt->rangeBeginVal = rangeBeginVal;
            stmt->rangeEndVal = rangeEndVal;

            checkStmt(stmt->body);


            PopOuterStmt(stmt);
        }

        void visitSwitchStmt(SwitchStmt* stmt)
        {
            PushOuterStmt(stmt);
            // TODO(tfoley): need to coerce condition to an integral type...
            stmt->condition = CheckExpr(stmt->condition);
            checkStmt(stmt->body);

            // TODO(tfoley): need to check that all case tags are unique

            // TODO(tfoley): check that there is at most one `default` clause

            PopOuterStmt(stmt);
        }
        void visitCaseStmt(CaseStmt* stmt)
        {
            // TODO(tfoley): Need to coerce to type being switch on,
            // and ensure that value is a compile-time constant
            auto expr = CheckExpr(stmt->expr);
            auto switchStmt = FindOuterStmt<SwitchStmt>();

            if (!switchStmt)
            {
                getSink()->diagnose(stmt, Diagnostics::caseOutsideSwitch);
            }
            else
            {
                // TODO: need to do some basic matching to ensure the type
                // for the `case` is consistent with the type for the `switch`...
            }

            stmt->expr = expr;
            stmt->parentStmt = switchStmt;
        }
        void visitDefaultStmt(DefaultStmt* stmt)
        {
            auto switchStmt = FindOuterStmt<SwitchStmt>();
            if (!switchStmt)
            {
                getSink()->diagnose(stmt, Diagnostics::defaultOutsideSwitch);
            }
            stmt->parentStmt = switchStmt;
        }
        void visitIfStmt(IfStmt *stmt)
        {
            stmt->Predicate = checkPredicateExpr(stmt->Predicate);
            checkStmt(stmt->PositiveStatement);
            checkStmt(stmt->NegativeStatement);
        }

        void visitUnparsedStmt(UnparsedStmt*)
        {
            // Nothing to do
        }

        void visitEmptyStmt(EmptyStmt*)
        {
            // Nothing to do
        }

        void visitDiscardStmt(DiscardStmt*)
        {
            // Nothing to do
        }

        void visitReturnStmt(ReturnStmt *stmt)
        {
            if (!stmt->Expression)
            {
                if (function && !function->ReturnType.Equals(getSession()->getVoidType()))
                {
                    getSink()->diagnose(stmt, Diagnostics::returnNeedsExpression);
                }
            }
            else
            {
                stmt->Expression = CheckTerm(stmt->Expression);
                if (!stmt->Expression->type->Equals(getSession()->getErrorType()))
                {
                    if (function)
                    {
                        stmt->Expression = Coerce(function->ReturnType.Ptr(), stmt->Expression);
                    }
                    else
                    {
                        // TODO(tfoley): this case currently gets triggered for member functions,
                        // which aren't being checked consistently (because of the whole symbol
                        // table idea getting in the way).

//							getSink()->diagnose(stmt, Diagnostics::unimplemented, "case for return stmt");
                    }
                }
            }
        }

        IntegerLiteralValue GetMinBound(RefPtr<IntVal> val)
        {
            if (auto constantVal = val.As<ConstantIntVal>())
                return constantVal->value;

            // TODO(tfoley): Need to track intervals so that this isn't just a lie...
            return 1;
        }

        void maybeInferArraySizeForVariable(Variable* varDecl)
        {
            // Not an array?
            auto arrayType = varDecl->type->AsArrayType();
            if (!arrayType) return;

            // Explicit element count given?
            auto elementCount = arrayType->ArrayLength;
            if (elementCount) return;

            // No initializer?
            auto initExpr = varDecl->initExpr;
            if(!initExpr) return;

            // Is the initializer an initializer list?
            if(auto initializerListExpr = initExpr.As<InitializerListExpr>())
            {
                auto argCount = initializerListExpr->args.Count();
                elementCount = new ConstantIntVal(argCount);
            }
            // Is the type of the initializer an array type?
            else if(auto arrayInitType = initExpr->type->As<ArrayExpressionType>())
            {
                elementCount = arrayInitType->ArrayLength;
            }
            else
            {
                // Nothing to do: we couldn't infer a size
                return;
            }

            // Create a new array type based on the size we found,
            // and install it into our type.
            varDecl->type.type = getArrayType(
                arrayType->baseType,
                elementCount);
        }

        void ValidateArraySizeForVariable(Variable* varDecl)
        {
            auto arrayType = varDecl->type->AsArrayType();
            if (!arrayType) return;

            auto elementCount = arrayType->ArrayLength;
            if (!elementCount)
            {
                // Note(tfoley): For now we allow arrays of unspecified size
                // everywhere, because some source languages (e.g., GLSL)
                // allow them in specific cases.
#if 0
                getSink()->diagnose(varDecl, Diagnostics::invalidArraySize);
#endif
                return;
            }

            // TODO(tfoley): How to handle the case where bound isn't known?
            if (GetMinBound(elementCount) <= 0)
            {
                getSink()->diagnose(varDecl, Diagnostics::invalidArraySize);
                return;
            }
        }

        void visitVariable(Variable* varDecl)
        {
            if (function || checkingPhase == CheckingPhase::Header)
            {
                TypeExp typeExp = CheckUsableType(varDecl->type);
#if 0
                if (typeExp.type->GetBindableResourceType() != BindableResourceType::NonBindable)
                {
                    // We don't want to allow bindable resource types as local variables (at least for now).
                    auto parentDecl = varDecl->ParentDecl;
                    if (auto parentScopeDecl = dynamic_cast<ScopeDecl*>(parentDecl))
                    {
                        getSink()->diagnose(varDecl->type, Diagnostics::invalidTypeForLocalVariable);
                    }
                }
#endif
                varDecl->type = typeExp;
                if (varDecl->type.Equals(getSession()->getVoidType()))
                {
                    getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
                }
            }

            if (checkingPhase == CheckingPhase::Body)
            {
                if (auto initExpr = varDecl->initExpr)
                {
                    initExpr = CheckTerm(initExpr);
                    varDecl->initExpr = initExpr;
                }

                // If this is an array variable, then we first want to give
                // it a chance to infer an array size from its initializer
                //
                // TODO(tfoley): May need to extend this to handle the
                // multi-dimensional case...
                maybeInferArraySizeForVariable(varDecl);
                //
                // Next we want to make sure that the declared (or inferred)
                // size for the array meets whatever language-specific
                // constraints we want to enforce (e.g., disallow empty
                // arrays in specific cases)
                ValidateArraySizeForVariable(varDecl);


                if (auto initExpr = varDecl->initExpr)
                {
                    // TODO(tfoley): should coercion of initializer lists be special-cased
                    // here, or handled as a general case for coercion?

                    initExpr = Coerce(varDecl->type.Ptr(), initExpr);
                    varDecl->initExpr = initExpr;
                }
            }
            varDecl->SetCheckState(getCheckedState());
        }

        void visitWhileStmt(WhileStmt *stmt)
        {
            PushOuterStmt(stmt);
            stmt->Predicate = checkPredicateExpr(stmt->Predicate);
            checkStmt(stmt->Statement);
            PopOuterStmt(stmt);
        }
        void visitExpressionStmt(ExpressionStmt *stmt)
        {
            stmt->Expression = CheckExpr(stmt->Expression);
        }

        RefPtr<Expr> visitBoolLiteralExpr(BoolLiteralExpr* expr)
        {
            expr->type = getSession()->getBoolType();
            return expr;
        }

        RefPtr<Expr> visitIntegerLiteralExpr(IntegerLiteralExpr* expr)
        {
            // The expression might already have a type, determined by its suffix.
            // It it doesn't, we will give it a default type.
            //
            // TODO: We should be careful to pick a "big enough" type
            // based on the size of the value (e.g., don't try to stuff
            // a constant in an `int` if it requires 64 or more bits).
            //
            // The long-term solution here is to give a type to a literal
            // based on the context where it is used, but that requires
            // a more sophisticated type system than we have today.
            //
            if(!expr->type.type)
            {
                expr->type = getSession()->getIntType();
            }
            return expr;
        }

        RefPtr<Expr> visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr)
        {
            if(!expr->type.type)
            {
                expr->type = getSession()->getFloatType();
            }
            return expr;
        }

        RefPtr<Expr> visitStringLiteralExpr(StringLiteralExpr* expr)
        {
            expr->type = getSession()->getStringType();
            return expr;
        }

        IntVal* GetIntVal(IntegerLiteralExpr* expr)
        {
            // TODO(tfoley): don't keep allocating here!
            return new ConstantIntVal(expr->value);
        }

        Name* getName(String const& text)
        {
            return getCompileRequest()->getNamePool()->getName(text);
        }

        RefPtr<IntVal> TryConstantFoldExpr(
            InvokeExpr* invokeExpr)
        {
            // We need all the operands to the expression

            // Check if the callee is an operation that is amenable to constant-folding.
            //
            // For right now we will look for calls to intrinsic functions, and then inspect
            // their names (this is bad and slow).
            auto funcDeclRefExpr = invokeExpr->FunctionExpr.As<DeclRefExpr>();
            if (!funcDeclRefExpr) return nullptr;

            auto funcDeclRef = funcDeclRefExpr->declRef;
            auto intrinsicMod = funcDeclRef.getDecl()->FindModifier<IntrinsicOpModifier>();
            if (!intrinsicMod)
            {
                // We can't constant fold anything that doesn't map to a builtin
                // operation right now.
                //
                // TODO: we should really allow constant-folding for anything
                // that can be lowerd to our bytecode...
                return nullptr;
            }



            // Let's not constant-fold operations with more than a certain number of arguments, for simplicity
            static const int kMaxArgs = 8;
            if (invokeExpr->Arguments.Count() > kMaxArgs)
                return nullptr;

            // Before checking the operation name, let's look at the arguments
            RefPtr<IntVal> argVals[kMaxArgs];
            IntegerLiteralValue constArgVals[kMaxArgs];
            int argCount = 0;
            bool allConst = true;
            for (auto argExpr : invokeExpr->Arguments)
            {
                auto argVal = TryCheckIntegerConstantExpression(argExpr.Ptr());
                if (!argVal)
                    return nullptr;

                argVals[argCount] = argVal;

                if (auto constArgVal = argVal.As<ConstantIntVal>())
                {
                    constArgVals[argCount] = constArgVal->value;
                }
                else
                {
                    allConst = false;
                }
                argCount++;
            }

            if (!allConst)
            {
                // TODO(tfoley): We probably want to support a very limited number of operations
                // on "constants" that aren't actually known, to be able to handle a generic
                // that takes an integer `N` but then constructs a vector of size `N+1`.
                //
                // The hard part there is implementing the rules for value unification in the
                // presence of more complicated `IntVal` subclasses, like `SumIntVal`. You'd
                // need inference to be smart enough to know that `2 + N` and `N + 2` are the
                // same value, as are `N + M + 1 + 1` and `M + 2 + N`.
                //
                // For now we can just bail in this case.
                return nullptr;
            }

            // At this point, all the operands had simple integer values, so we are golden.
            IntegerLiteralValue resultValue = 0;
            auto opName = funcDeclRef.GetName();

            // handle binary operators
            if (opName == getName("-"))
            {
                if (argCount == 1)
                {
                    resultValue = -constArgVals[0];
                }
                else if (argCount == 2)
                {
                    resultValue = constArgVals[0] - constArgVals[1];
                }
            }

            // simple binary operators
#define CASE(OP)                                                    \
            else if(opName == getName(#OP)) do {                    \
                if(argCount != 2) return nullptr;                   \
                resultValue = constArgVals[0] OP constArgVals[1];   \
            } while(0)

            CASE(+); // TODO: this can also be unary...
            CASE(*);
#undef CASE

            // binary operators with chance of divide-by-zero
            // TODO: issue a suitable error in that case
#define CASE(OP)                                                    \
            else if(opName == getName(#OP)) do {                    \
                if(argCount != 2) return nullptr;                   \
                if(!constArgVals[1]) return nullptr;                \
                resultValue = constArgVals[0] OP constArgVals[1];   \
            } while(0)

            CASE(/);
            CASE(%);
#undef CASE

            // TODO(tfoley): more cases
            else
            {
                return nullptr;
            }

            RefPtr<IntVal> result = new ConstantIntVal(resultValue);
            return result;
        }

        RefPtr<IntVal> TryConstantFoldExpr(
            Expr* expr)
        {
            // Unwrap any "identity" expressions
            while (auto parenExpr = dynamic_cast<ParenExpr*>(expr))
            {
                expr = parenExpr->base;
            }

            // TODO(tfoley): more serious constant folding here
            if (auto intLitExpr = dynamic_cast<IntegerLiteralExpr*>(expr))
            {
                return GetIntVal(intLitExpr);
            }

            // it is possible that we are referring to a generic value param
            if (auto declRefExpr = dynamic_cast<DeclRefExpr*>(expr))
            {
                auto declRef = declRefExpr->declRef;

                if (auto genericValParamRef = declRef.As<GenericValueParamDecl>())
                {
                    // TODO(tfoley): handle the case of non-`int` value parameters...
                    return new GenericParamIntVal(genericValParamRef);
                }

                // We may also need to check for references to variables that
                // are defined in a way that can be used as a constant expression:
                if(auto varRef = declRef.As<VarDeclBase>())
                {
                    auto varDecl = varRef.getDecl();

                    switch(getSourceLanguage())
                    {
                    default:
                    case SourceLanguage::Slang:
                    case SourceLanguage::HLSL:
                        // HLSL: `static const` is used to mark compile-time constant expressions
                        if(auto staticAttr = varDecl->FindModifier<HLSLStaticModifier>())
                        {
                            if(auto constAttr = varDecl->FindModifier<ConstModifier>())
                            {
                                // HLSL `static const` can be used as a constant expression
                                if(auto initExpr = getInitExpr(varRef))
                                {
                                    return TryConstantFoldExpr(initExpr.Ptr());
                                }
                            }
                        }
                        break;

                    case SourceLanguage::GLSL:
                        // GLSL: `const` indicates compile-time constant expression
                        //
                        // TODO(tfoley): The current logic here isn't robust against
                        // GLSL "specialization constants" - we will extract the
                        // initializer for a `const` variable and use it to extract
                        // a value, when we really should be using an opaque
                        // reference to the variable.
                        if(auto constAttr = varDecl->FindModifier<ConstModifier>())
                        {
                            // We need to handle a "specialization constant" (with a `constant_id` layout modifier)
                            // differently from an ordinary compile-time constant. The latter can/should be reduced
                            // to a value, while the former should be kept as a symbolic reference

                            if(auto constantIDModifier = varDecl->FindModifier<GLSLConstantIDLayoutModifier>())
                            {
                                // Retain the specialization constant as a symbolic reference
                                //
                                // TODO(tfoley): handle the case of non-`int` value parameters...
                                //
                                // TODO(tfoley): this is cloned from the case above that handles generic value parameters
                                return new GenericParamIntVal(varRef);
                            }
                            else if(auto initExpr = getInitExpr(varRef))
                            {
                                // This is an ordinary constant, and not a specialization constant, so we
                                // can try to fold its value right now.
                                return TryConstantFoldExpr(initExpr.Ptr());
                            }
                        }
                        break;
                    }

                }
            }

            if(auto castExpr = dynamic_cast<TypeCastExpr*>(expr))
            {
                auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr());
                if(val)
                    return val;
            }
            else if (auto invokeExpr = dynamic_cast<InvokeExpr*>(expr))
            {
                auto val = TryConstantFoldExpr(invokeExpr);
                if (val)
                    return val;
            }

            return nullptr;
        }

        // Try to check an integer constant expression, either returning the value,
        // or NULL if the expression isn't recognized as a constant.
        RefPtr<IntVal> TryCheckIntegerConstantExpression(Expr* exp)
        {
            // Check if type is acceptable for an integer constant expression
            if(auto basicType = exp->type.type->As<BasicExpressionType>())
            {
                if(!isIntegerBaseType(basicType->baseType))
                    return nullptr;
            }
            else
            {
                return nullptr;
            }

            // Consider operations that we might be able to constant-fold...
            return TryConstantFoldExpr(exp);
        }

        // Enforce that an expression resolves to an integer constant, and get its value
        RefPtr<IntVal> CheckIntegerConstantExpression(Expr* inExpr)
        {
            // No need to issue further errors if the expression didn't even type-check.
            if(IsErrorExpr(inExpr)) return nullptr;

            // First coerce the expression to the expected type
            auto expr = Coerce(getSession()->getIntType(),inExpr);

            // No need to issue further errors if the type coercion failed.
            if(IsErrorExpr(expr)) return nullptr;

            auto result = TryCheckIntegerConstantExpression(expr.Ptr());
            if (!result)
            {
                getSink()->diagnose(expr, Diagnostics::expectedIntegerConstantNotConstant);
            }
            return result;
        }



        RefPtr<Expr> CheckSimpleSubscriptExpr(
            RefPtr<IndexExpr>   subscriptExpr,
            RefPtr<Type>              elementType)
        {
            auto baseExpr = subscriptExpr->BaseExpression;
            auto indexExpr = subscriptExpr->IndexExpression;

            if (!indexExpr->type->Equals(getSession()->getIntType()) &&
                !indexExpr->type->Equals(getSession()->getUIntType()))
            {
                getSink()->diagnose(indexExpr, Diagnostics::subscriptIndexNonInteger);
                return CreateErrorExpr(subscriptExpr.Ptr());
            }

            subscriptExpr->type = QualType(elementType);

            // TODO(tfoley): need to be more careful about this stuff
            subscriptExpr->type.IsLeftValue = baseExpr->type.IsLeftValue;

            return subscriptExpr;
        }

        // The way that we have designed out type system, pretyt much *every*
        // type is a reference to some declaration in the standard library.
        // That means that when we construct a new type on the fly, we need
        // to make sure that it is wired up to reference the appropriate
        // declaration, or else it won't compare as equal to other types
        // that *do* reference the declaration.
        //
        // This function is used to construct a `vector<T,N>` type
        // programmatically, so that it will work just like a type of
        // that form constructed by the user.
        RefPtr<VectorExpressionType> createVectorType(
            RefPtr<Type>  elementType,
            RefPtr<IntVal>          elementCount)
        {
            auto session = getSession();
            auto vectorGenericDecl = findMagicDecl(
                session, "Vector").As<GenericDecl>();
            auto vectorTypeDecl = vectorGenericDecl->inner;

            auto substitutions = new GenericSubstitution();
            substitutions->genericDecl = vectorGenericDecl.Ptr();
            substitutions->args.Add(elementType);
            substitutions->args.Add(elementCount);

            auto declRef = DeclRef<Decl>(vectorTypeDecl.Ptr(), substitutions);

            return DeclRefType::Create(
                session,
                declRef)->As<VectorExpressionType>();
        }

        RefPtr<Expr> visitIndexExpr(IndexExpr* subscriptExpr)
        {
            auto baseExpr = subscriptExpr->BaseExpression;
            baseExpr = CheckExpr(baseExpr);

            RefPtr<Expr> indexExpr = subscriptExpr->IndexExpression;
            if (indexExpr)
            {
                indexExpr = CheckExpr(indexExpr);
            }

            subscriptExpr->BaseExpression = baseExpr;
            subscriptExpr->IndexExpression = indexExpr;

            // If anything went wrong in the base expression,
            // then just move along...
            if (IsErrorExpr(baseExpr))
                return CreateErrorExpr(subscriptExpr);

            // Otherwise, we need to look at the type of the base expression,
            // to figure out how subscripting should work.
            auto baseType = baseExpr->type.Ptr();
            if (auto baseTypeType = baseType->As<TypeType>())
            {
                // We are trying to "index" into a type, so we have an expression like `float[2]`
                // which should be interpreted as resolving to an array type.

                RefPtr<IntVal> elementCount = nullptr;
                if (indexExpr)
                {
                    elementCount = CheckIntegerConstantExpression(indexExpr.Ptr());
                }

                auto elementType = CoerceToUsableType(TypeExp(baseExpr, baseTypeType->type));
                auto arrayType = getArrayType(
                    elementType,
                    elementCount);

                typeResult = arrayType;
                subscriptExpr->type = QualType(getTypeType(arrayType));
                return subscriptExpr;
            }
            else if (auto baseArrayType = baseType->As<ArrayExpressionType>())
            {
                return CheckSimpleSubscriptExpr(
                    subscriptExpr,
                    baseArrayType->baseType);
            }
            else if (auto vecType = baseType->As<VectorExpressionType>())
            {
                return CheckSimpleSubscriptExpr(
                    subscriptExpr,
                    vecType->elementType);
            }
            else if (auto matType = baseType->As<MatrixExpressionType>())
            {
                // TODO(tfoley): We shouldn't go and recompute
                // row types over and over like this... :(
                auto rowType = createVectorType(
                    matType->getElementType(),
                    matType->getColumnCount());

                return CheckSimpleSubscriptExpr(
                    subscriptExpr,
                    rowType);
            }

            // Default behavior is to look at all available `__subscript`
            // declarations on the type and try to call one of them.

            {
                LookupResult lookupResult = lookUpMember(
                    getSession(),
                    this,
                    getName("operator[]"),
                    baseType);
                if (!lookupResult.isValid())
                {
                    goto fail;
                }

                // Now that we know there is at least one subscript member,
                // we will construct a reference to it and try to call it.
                //
                // Note: the expression may be an `OverloadedExpr`, in which
                // case the attempt to call it will trigger overload
                // resolution.
                RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr(
                    lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc);

                RefPtr<InvokeExpr> subscriptCallExpr = new InvokeExpr();
                subscriptCallExpr->loc = subscriptExpr->loc;
                subscriptCallExpr->FunctionExpr = subscriptFuncExpr;

                // TODO(tfoley): This path can support multiple arguments easily
                subscriptCallExpr->Arguments.Add(subscriptExpr->IndexExpression);

                return CheckInvokeExprWithCheckedOperands(subscriptCallExpr.Ptr());
            }

        fail:
            {
                getSink()->diagnose(subscriptExpr, Diagnostics::subscriptNonArray, baseType);
                return CreateErrorExpr(subscriptExpr);
            }
        }

        bool MatchArguments(FuncDecl * functionNode, List <RefPtr<Expr>> &args)
        {
            if (functionNode->GetParameters().Count() != args.Count())
                return false;
            int i = 0;
            for (auto param : functionNode->GetParameters())
            {
                if (!param->type.Equals(args[i]->type.Ptr()))
                    return false;
                i++;
            }
            return true;
        }

        // Coerce an expression to a specific  type that it is expected to have in context
        RefPtr<Expr> CoerceExprToType(
            RefPtr<Expr>	expr,
            RefPtr<Type>			type)
        {
            // TODO(tfoley): clean this up so there is only one version...
            return Coerce(type, expr);
        }

        RefPtr<Expr> visitParenExpr(ParenExpr* expr)
        {
            auto base = expr->base;
            base = CheckTerm(base);

            expr->base = base;
            expr->type = base->type;
            return expr;
        }

        //

            /// Given an immutable `expr` used as an l-value emit a special diagnostic if it was derived from `this`.
        void maybeDiagnoseThisNotLValue(Expr* expr)
        {
            // We will try to handle expressions of the form:
            //
            //      e ::= "this"
            //          | e . name
            //          | e [ expr ]
            //
            // We will unwrap the `e.name` and `e[expr]` cases in a loop.
            RefPtr<Expr> e = expr;
            for(;;)
            {
                if(auto memberExpr = e.As<MemberExpr>())
                {
                    e = memberExpr->BaseExpression;
                }
                else if(auto subscriptExpr = e.As<IndexExpr>())
                {
                    e = subscriptExpr->BaseExpression;
                }
                else
                {
                    break;
                }
            }
            //
            // Now we check to see if we have a `this` expression,
            // and if it is immutable.
            if(auto thisExpr = e.As<ThisExpr>())
            {
                if(!thisExpr->type.IsLeftValue)
                {
                    getSink()->diagnose(thisExpr, Diagnostics::thisIsImmutableByDefault);
                }
            }
        }

        RefPtr<Expr> visitAssignExpr(AssignExpr* expr)
        {
            expr->left = CheckExpr(expr->left);

            auto type = expr->left->type;

            expr->right = Coerce(type, CheckTerm(expr->right));

            if (!type.IsLeftValue)
            {
                if (type->As<ErrorType>())
                {
                    // Don't report an l-value issue on an errorneous expression
                }
                else
                {
                    getSink()->diagnose(expr, Diagnostics::assignNonLValue);

                    // As a special case, check if the LHS expression is derived
                    // from a `this` parameter (implicitly or explicitly), which
                    // is immutable. We can give the user a bit more context into
                    // what is going on.
                    //
                    maybeDiagnoseThisNotLValue(expr->left);
                }
            }
            expr->type = type;
            return expr;
        }

        void registerExtension(ExtensionDecl* decl)
        {
            if (decl->IsChecked(DeclCheckState::CheckedHeader))
                return;

            decl->SetCheckState(DeclCheckState::CheckingHeader);
            decl->targetType = CheckProperType(decl->targetType);
            decl->SetCheckState(DeclCheckState::CheckedHeader);

            // TODO: need to check that the target type names a declaration...

            if (auto targetDeclRefType = decl->targetType->As<DeclRefType>())
            {
                // Attach our extension to that type as a candidate...
                if (auto aggTypeDeclRef = targetDeclRefType->declRef.As<AggTypeDecl>())
                {
                    auto aggTypeDecl = aggTypeDeclRef.getDecl();
                    decl->nextCandidateExtension = aggTypeDecl->candidateExtensions;
                    aggTypeDecl->candidateExtensions = decl;
                    return;
                }
            }
            getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here");
        }

        void visitExtensionDecl(ExtensionDecl* decl)
        {
            if (decl->IsChecked(getCheckedState())) return;

            if (!decl->targetType->As<DeclRefType>())
            {
                getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here");
            }
            // now check the members of the extension
            for (auto m : decl->Members)
            {
                checkDecl(m);
            }
            decl->SetCheckState(getCheckedState());
        }

        // Figure out what type an initializer/constructor declaration
        // is supposed to return. In most cases this is just the type
        // declaration that its declaration is nested inside.
        RefPtr<Type> findResultTypeForConstructorDecl(ConstructorDecl* decl)
        {
            // We want to look at the parent of the declaration,
            // but if the declaration is generic, the parent will be
            // the `GenericDecl` and we need to skip past that to
            // the grandparent.
            //
            auto parent = decl->ParentDecl;
            auto genericParent = dynamic_cast<GenericDecl*>(parent);
            if (genericParent)
            {
                parent = genericParent->ParentDecl;
            }

            // Now look at the type of the parent (or grandparent).
            if (auto aggTypeDecl = dynamic_cast<AggTypeDecl*>(parent))
            {
                // We are nested in an aggregate type declaration,
                // so the result type of the initializer will just
                // be the surrounding type.
                return DeclRefType::Create(
                    getSession(),
                    makeDeclRef(aggTypeDecl));
            }
            else if (auto extDecl = dynamic_cast<ExtensionDecl*>(parent))
            {
                // We are nested inside an extension, so the result
                // type needs to be the type being extended.
                return extDecl->targetType.type;
            }
            else
            {
                getSink()->diagnose(decl, Diagnostics::initializerNotInsideType);
                return nullptr;
            }
        }

        void visitConstructorDecl(ConstructorDecl* decl)
        {
            if (decl->IsChecked(getCheckedState())) return;
            if (checkingPhase == CheckingPhase::Header)
            {
                decl->SetCheckState(DeclCheckState::CheckingHeader);

                for (auto& paramDecl : decl->GetParameters())
                {
                    paramDecl->type = CheckUsableType(paramDecl->type);
                }

                // We need to compute the result tyep for this declaration,
                // since it wasn't filled in for us.
                decl->ReturnType.type = findResultTypeForConstructorDecl(decl);
            }
            else
            {
                // TODO(tfoley): check body
            }
            decl->SetCheckState(getCheckedState());
        }


        void visitSubscriptDecl(SubscriptDecl* decl)
        {
            if (decl->IsChecked(getCheckedState())) return;
            for (auto& paramDecl : decl->GetParameters())
            {
                paramDecl->type = CheckUsableType(paramDecl->type);
            }

            decl->ReturnType = CheckUsableType(decl->ReturnType);

            // If we have a subscript declaration with no accessor declarations,
            // then we should create a single `GetterDecl` to represent
            // the implicit meaning of their declaration, so:
            //
            //      subscript(uint index) -> T;
            //
            // becomes:
            //
            //      subscript(uint index) -> T { get; }
            //

            bool anyAccessors = false;
            for(auto accessorDecl : decl->getMembersOfType<AccessorDecl>())
            {
                anyAccessors = true;
            }

            if(!anyAccessors)
            {
                RefPtr<GetterDecl> getterDecl = new GetterDecl();
                getterDecl->loc = decl->loc;

                getterDecl->ParentDecl = decl;
                decl->Members.Add(getterDecl);
            }

            for(auto mm : decl->Members)
            {
                checkDecl(mm);
            }

            decl->SetCheckState(getCheckedState());
        }

        void visitAccessorDecl(AccessorDecl* decl)
        {
            if (checkingPhase == CheckingPhase::Header)
            {
                // An acessor must appear nested inside a subscript declaration (today),
                // or a property declaration (when we add them). It will derive
                // its return type from the outer declaration, so we handle both
                // of these checks at the same place.
                auto parent = decl->ParentDecl;
                if (auto parentSubscript = dynamic_cast<SubscriptDecl*>(parent))
                {
                    decl->ReturnType = parentSubscript->ReturnType;
                }
                // TODO: when we add "property" declarations, check for them here
                else
                {
                    getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty);
                }

            }
            else
            {
                // TODO: check the body!
            }
            decl->SetCheckState(getCheckedState());
        }


        //

        struct Constraint
        {
            Decl*		decl; // the declaration of the thing being constraints
            RefPtr<Val>	val; // the value to which we are constraining it
            bool satisfied = false; // Has this constraint been met?
        };

        // A collection of constraints that will need to be satisified (solved)
        // in order for checking to suceed.
        struct ConstraintSystem
        {
            // A source location to use in reporting any issues
            SourceLoc loc;

            // The generic declaration whose parameters we
            // are trying to solve for.
            RefPtr<GenericDecl> genericDecl;

            // Constraints we have accumulated, which constrain
            // the possible arguments for those parameters.
            List<Constraint> constraints;
        };

        RefPtr<Type> TryJoinVectorAndScalarType(
            RefPtr<VectorExpressionType> vectorType,
            RefPtr<BasicExpressionType>  scalarType)
        {
            // Join( vector<T,N>, S ) -> vetor<Join(T,S), N>
            //
            // That is, the join of a vector and a scalar type is
            // a vector type with a joined element type.
            auto joinElementType = TryJoinTypes(
                vectorType->elementType,
                scalarType);
            if(!joinElementType)
                return nullptr;

            return createVectorType(
                joinElementType,
                vectorType->elementCount);
        }

        struct TypeWitnessBreadcrumb
        {
            TypeWitnessBreadcrumb*  prev;

            RefPtr<Type>            sub;
            RefPtr<Type>            sup;
            DeclRef<Decl>           declRef;
        };

        // Crete a subtype witness based on the declared relationship
        // found in a single breadcrumb
        RefPtr<DeclaredSubtypeWitness> createSimpleSubtypeWitness(
            TypeWitnessBreadcrumb*  breadcrumb)
        {
            RefPtr<DeclaredSubtypeWitness> witness = new DeclaredSubtypeWitness();
            witness->sub = breadcrumb->sub;
            witness->sup = breadcrumb->sup;
            witness->declRef = breadcrumb->declRef;
            return witness;
        }

        RefPtr<Val> createTypeWitness(
            RefPtr<Type>            type,
            DeclRef<InterfaceDecl>  interfaceDeclRef,
            TypeWitnessBreadcrumb*  inBreadcrumbs)
        {
            if(!inBreadcrumbs)
            {
                // We need to construct a witness to the fact
                // that `type` has been proven to be *equal*
                // to `interfaceDeclRef`.
                //
                SLANG_UNEXPECTED("reflexive type witness");
                UNREACHABLE_RETURN(nullptr);
            }

            // We might have one or more steps in the breadcrumb trail, e.g.:
            //
            //      {A : B} {B : C} {C : D}
            //
            // The chain is stored as a reversed linked list, so that
            // the first entry would be the `(C : D)` relationship
            // above.
            //
            // We need to walk the list and build up a suitable witness,
            // which in the above case would look like:
            //
            //      Transitive(
            //          Transitive(
            //              Declared({A : B}),
            //              {B : C}),
            //          {C : D})
            //
            // Because of the ordering of the breadcrumb trail, along
            // with the way the `Transitive` case nests, we will be
            // building these objects outside-in, and keeping
            // track of the "hole" where the next step goes.
            //
            auto bb = inBreadcrumbs;

            // `witness` here will hold the first (outer-most) object
            // we create, which is the overall result.
            RefPtr<SubtypeWitness> witness;

            // `link` will point at the remaining "hole" in the
            // data structure, to be filled in.
            RefPtr<SubtypeWitness>* link = &witness;

            // As long as there is more than one breadcrumb, we
            // need to be creating transitie witnesses.
            while(bb->prev)
            {
                // On the first iteration when processing the list
                // above, the breadcrumb would be for `{ C : D }`,
                // and so we'd create:
                //
                //      Transitive(
                //          [...],
                //          { C : D})
                //
                // where `[...]` represents the "hole" we leave
                // open to fill in next.
                //
                RefPtr<TransitiveSubtypeWitness> transitiveWitness = new TransitiveSubtypeWitness();
                transitiveWitness->sub = bb->sub;
                transitiveWitness->sup = bb->sup;
                transitiveWitness->midToSup = bb->declRef;

                // Fill in the current hole, and then set the
                // hole to point into the node we just created.
                *link = transitiveWitness;
                link = &transitiveWitness->subToMid;

                // Move on with the list.
                bb = bb->prev;
            }

            // If we exit the loop, then there is only one breadcrumb left.
            // In our running example this would be `{ A : B }`. We create
            // a simple (declared) subtype witness for it, and plug the
            // final hole, after which there shouldn't be a hole to deal with.
            RefPtr<DeclaredSubtypeWitness> declaredWitness = createSimpleSubtypeWitness(bb);
            *link = declaredWitness;

            // We now know that our original `witness` variable has been
            // filled in, and there are no other holes.
            return witness;
        }

        bool doesTypeConformToInterfaceImpl(
            RefPtr<Type>            originalType,
            RefPtr<Type>            type,
            DeclRef<InterfaceDecl>  interfaceDeclRef,
            RefPtr<Val>*            outWitness,
            TypeWitnessBreadcrumb*  inBreadcrumbs)
        {
            // for now look up a conformance member...
            if(auto declRefType = type->As<DeclRefType>())
            {
                auto declRef = declRefType->declRef;

                // Easy case: a type conforms to itself.
                //
                // TODO: This is actually a bit more complicated, as
                // the interface needs to be "object-safe" for us to
                // really make this determination...
                if(declRef == interfaceDeclRef)
                {
                    if(outWitness)
                    {
                        *outWitness = createTypeWitness(originalType, interfaceDeclRef, inBreadcrumbs);
                    }
                    return true;
                }

                if( auto aggTypeDeclRef = declRef.As<AggTypeDecl>() )
                {
                    checkDecl(aggTypeDeclRef.getDecl());

                    for( auto inheritanceDeclRef : getMembersOfTypeWithExt<InheritanceDecl>(aggTypeDeclRef))
                    {
                        checkDecl(inheritanceDeclRef.getDecl());

                        // Here we will recursively look up conformance on the type
                        // that is being inherited from. This is dangerous because
                        // it might lead to infinite loops.
                        //
                        // TODO: A better appraoch would be to create a linearized list
                        // of all the interfaces that a given type direclty or indirectly
                        // inheirts, and store it with the type, so that we don't have
                        // to recurse in places like this (and can maybe catch infinite
                        // loops better). This would also help avoid checking multiply-inherited
                        // conformances multiple times.

                        auto inheritedType = getBaseType(inheritanceDeclRef);

                        // We need to ensure that the witness that gets created
                        // is a composite one, reflecting lookup through
                        // the inheritance declaration.
                        TypeWitnessBreadcrumb breadcrumb;
                        breadcrumb.prev = inBreadcrumbs;

                        breadcrumb.sub = type;
                        breadcrumb.sup = inheritedType;
                        breadcrumb.declRef = inheritanceDeclRef;

                        if(doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb))
                        {
                            return true;
                        }
                    }
                    // if an inheritance decl is not found, try to find a GenericTypeConstraintDecl
                    for (auto genConstraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(aggTypeDeclRef))
                    {
                        checkDecl(genConstraintDeclRef.getDecl());
                        auto inheritedType = GetSup(genConstraintDeclRef);
                        TypeWitnessBreadcrumb breadcrumb;
                        breadcrumb.prev = inBreadcrumbs;
                        breadcrumb.sub = type;
                        breadcrumb.sup = inheritedType;
                        breadcrumb.declRef = genConstraintDeclRef;
                        if (doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb))
                        {
                            return true;
                        }
                    }
                }
                else if( auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>() )
                {
                    // We need to enumerate the constraints placed on this type by its outer
                    // generic declaration, and see if any of them guarantees that we
                    // satisfy the given interface..
                    auto genericDeclRef = genericTypeParamDeclRef.GetParent().As<GenericDecl>();
                    SLANG_ASSERT(genericDeclRef);

                    for( auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef) )
                    {
                        auto sub = GetSub(constraintDeclRef);
                        auto sup = GetSup(constraintDeclRef);

                        auto subDeclRef = sub->As<DeclRefType>();
                        if(!subDeclRef)
                            continue;
                        if(subDeclRef->declRef != genericTypeParamDeclRef)
                            continue;

                        // The witness that we create needs to reflect that
                        // it found the needed conformance by lookup through
                        // a generic type constraint.

                        TypeWitnessBreadcrumb breadcrumb;
                        breadcrumb.prev = inBreadcrumbs;
                        breadcrumb.sub = sub;
                        breadcrumb.sup = sup;
                        breadcrumb.declRef = constraintDeclRef;

                        if(doesTypeConformToInterfaceImpl(originalType, sup, interfaceDeclRef, outWitness, &breadcrumb))
                        {
                            return true;
                        }
                    }
                }
            }

            // default is failure
            return false;
        }

        bool DoesTypeConformToInterface(
            RefPtr<Type>  type,
            DeclRef<InterfaceDecl>        interfaceDeclRef)
        {
            return doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, nullptr, nullptr);
        }

        RefPtr<Val> tryGetInterfaceConformanceWitness(
            RefPtr<Type>  type,
            DeclRef<InterfaceDecl>        interfaceDeclRef)
        {
            RefPtr<Val> result;
            doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, &result, nullptr);
            return result;
        }

        /// Does there exist an implicit conversion from `fromType` to `toType`?
        bool canConvertImplicitly(
            RefPtr<Type> toType,
            RefPtr<Type> fromType)
        {
            // Can we convert at all?
            ConversionCost conversionCost;
            if(!CanCoerce(toType, fromType, &conversionCost))
                return false;

            // Is the conversion cheap enough to be done implicitly?
            if(conversionCost >= kConversionCost_GeneralConversion)
                return false;

            return true;
        }

        RefPtr<Type> TryJoinTypeWithInterface(
            RefPtr<Type>            type,
            DeclRef<InterfaceDecl>      interfaceDeclRef)
        {
            // The most basic test here should be: does the type declare conformance to the trait.
            if(DoesTypeConformToInterface(type, interfaceDeclRef))
                return type;

            // Just because `type` doesn't conform to the given `interfaceDeclRef`, that
            // doesn't necessarily indicate a failure. It is possible that we have a call
            // like `sqrt(2)` so that `type` is `int` and `interfaceDeclRef` is
            // `__BuiltinFloatingPointType`. The "obvious" answer is that we should infer
            // the type `float`, but it seems like the compiler would have to synthesize
            // that answer from thin air.
            //
            // A robsut/correct solution here might be to enumerate set of types types `S`
            // such that for each type `X` in `S`:
            //
            // * `type` is implicitly convertible to `X`
            // * `X` conforms to the interface named by `interfaceDeclRef`
            //
            // If the set `S` is non-empty then we would try to pick the "best" type from `S`.
            // The "best" type would be a type `Y` such that `Y` is implicitly convertible to
            // every other type in `S`.
            //
            // We are going to implement a much simpler strategy for now, where we only apply
            // the search process if `type` is a builtin scalar type, and then we only search
            // through types `X` that are also builtin scalar types.
            //
            RefPtr<Type> bestType;
            if(auto basicType = type.As<BasicExpressionType>())
            {
                for(Int baseTypeFlavorIndex = 0; baseTypeFlavorIndex < Int(BaseType::CountOf); baseTypeFlavorIndex++)
                {
                    // Don't consider `type`, since we already know it doesn't work.
                    if(baseTypeFlavorIndex == Int(basicType->baseType))
                        continue;

                    // Look up the type in our session.
                    auto candidateType = type->getSession()->getBuiltinType(BaseType(baseTypeFlavorIndex));
                    if(!candidateType)
                        continue;

                    // We only want to consider types that implement the target interface.
                    if(!DoesTypeConformToInterface(candidateType, interfaceDeclRef))
                        continue;

                    // We only want to consider types where we can implicitly convert from `type`
                    if(!canConvertImplicitly(candidateType, type))
                        continue;

                    // At this point, we have a candidate type that is usable.
                    //
                    // If this is our first viable candidate, then it is our best one:
                    //
                    if(!bestType)
                    {
                        bestType = candidateType;
                    }
                    else
                    {
                        // Otherwise, we want to pick the "better" type between `candidateType`
                        // and `bestType`.
                        //
                        // We are going to be a bit loose here, and not worry about the
                        // case where conversion is allowed in both directions.
                        //
                        // TODO: make this completely robust.
                        //
                        if(canConvertImplicitly(bestType, candidateType))
                        {
                            // Our candidate can convert to the current "best" type, so
                            // it is logically a more specific type that satisfies our
                            // constraints, thereforce we should keep it.
                            //
                            bestType = candidateType;
                        }
                    }
                }
                if(bestType)
                    return bestType;
            }

            // For all other cases, we will just bail out for now.
            //
            // TODO: In the future we should build some kind of side data structure
            // to accelerate either one or both of these queries:
            //
            // * Given a type `T`, what types `U` can it convert to implicitly?
            //
            // * Given an interface `I`, what types `U` conform to it?
            //
            // The intersection of the sets returned by these two queries is
            // the set of candidates we would like to consider here.

            return nullptr;
        }

        // Try to compute the "join" between two types
        RefPtr<Type> TryJoinTypes(
            RefPtr<Type>  left,
            RefPtr<Type>  right)
        {
            // Easy case: they are the same type!
            if (left->Equals(right))
                return left;

            // We can join two basic types by picking the "better" of the two
            if (auto leftBasic = left->As<BasicExpressionType>())
            {
                if (auto rightBasic = right->As<BasicExpressionType>())
                {
                    auto leftFlavor = leftBasic->baseType;
                    auto rightFlavor = rightBasic->baseType;

                    // TODO(tfoley): Need a special-case rule here that if
                    // either operand is of type `half`, then we promote
                    // to at least `float`

                    // Return the one that had higher rank...
                    if (leftFlavor > rightFlavor)
                        return left;
                    else
                    {
                        SLANG_ASSERT(rightFlavor > leftFlavor); // equality was handles at the top of this function
                        return right;
                    }
                }

                // We can also join a vector and a scalar
                if(auto rightVector = right->As<VectorExpressionType>())
                {
                    return TryJoinVectorAndScalarType(rightVector, leftBasic);
                }
            }

            // We can join two vector types by joining their element types
            // (and also their sizes...)
            if( auto leftVector = left->As<VectorExpressionType>())
            {
                if(auto rightVector = right->As<VectorExpressionType>())
                {
                    // Check if the vector sizes match
                    if(!leftVector->elementCount->EqualsVal(rightVector->elementCount.Ptr()))
                        return nullptr;

                    // Try to join the element types
                    auto joinElementType = TryJoinTypes(
                        leftVector->elementType,
                        rightVector->elementType);
                    if(!joinElementType)
                        return nullptr;

                    return createVectorType(
                        joinElementType,
                        leftVector->elementCount);
                }

                // We can also join a vector and a scalar
                if(auto rightBasic = right->As<BasicExpressionType>())
                {
                    return TryJoinVectorAndScalarType(leftVector, rightBasic);
                }
            }

            // HACK: trying to work trait types in here...
            if(auto leftDeclRefType = left->As<DeclRefType>())
            {
                if( auto leftInterfaceRef = leftDeclRefType->declRef.As<InterfaceDecl>() )
                {
                    //
                    return TryJoinTypeWithInterface(right, leftInterfaceRef);
                }
            }
            if(auto rightDeclRefType = right->As<DeclRefType>())
            {
                if( auto rightInterfaceRef = rightDeclRefType->declRef.As<InterfaceDecl>() )
                {
                    //
                    return TryJoinTypeWithInterface(left, rightInterfaceRef);
                }
            }

            // TODO: all the cases for vectors apply to matrices too!

            // Default case is that we just fail.
            return nullptr;
        }

        // Try to solve a system of generic constraints.
        // The `system` argument provides the constraints.
        // The `varSubst` argument provides the list of constraint
        // variables that were created for the system.
        //
        // Returns a new substitution representing the values that
        // we solved for along the way.
        SubstitutionSet TrySolveConstraintSystem(
            ConstraintSystem*		system,
            DeclRef<GenericDecl>          genericDeclRef)
        {
            // For now the "solver" is going to be ridiculously simplistic.

            // The generic itself will have some constraints, and for now we add these
            // to the system of constrains we will use for solving for the type variables.
            //
            // TODO: we need to decide whether constraints are used like this to influence
            // how we solve for type/value variables, or whether constraints in the parameter
            // list just work as a validation step *after* we've solved for the types.
            //
            // That is, should we allow `<T : Int>` to be written, and cause us to "infer"
            // that `T` should be the type `Int`? That seems a little silly.
            //
            // Eventually, though, we may want to support type identity constraints, especially
            // on associated types, like `<C where C : IContainer && C.IndexType == Int>`
            // These seem more reasonable to have influence constraint solving, since it could
            // conceivably let us specialize a `X<T> : IContainer` to `X<Int>` if we find
            // that `X<T>.IndexType == T`.
            for( auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef) )
            {
                if(!TryUnifyTypes(*system, GetSub(constraintDeclRef), GetSup(constraintDeclRef)))
                    return SubstitutionSet();
            }
            SubstitutionSet resultSubst = genericDeclRef.substitutions;
            // We will loop over the generic parameters, and for
            // each we will try to find a way to satisfy all
            // the constraints for that parameter
            List<RefPtr<Val>> args;
            for (auto m : getMembers(genericDeclRef))
            {
                if (auto typeParam = m.As<GenericTypeParamDecl>())
                {
                    RefPtr<Type> type = nullptr;
                    for (auto& c : system->constraints)
                    {
                        if (c.decl != typeParam.getDecl())
                            continue;

                        auto cType = c.val.As<Type>();
                        SLANG_RELEASE_ASSERT(cType.Ptr());

                        if (!type)
                        {
                            type = cType;
                        }
                        else
                        {
                            auto joinType = TryJoinTypes(type, cType);
                            if (!joinType)
                            {
                                // failure!
                                return SubstitutionSet();
                            }
                            type = joinType;
                        }

                        c.satisfied = true;
                    }

                    if (!type)
                    {
                        // failure!
                        return SubstitutionSet();
                    }
                    args.Add(type);
                }
                else if (auto valParam = m.As<GenericValueParamDecl>())
                {
                    // TODO(tfoley): maybe support more than integers some day?
                    // TODO(tfoley): figure out how this needs to interact with
                    // compile-time integers that aren't just constants...
                    RefPtr<IntVal> val = nullptr;
                    for (auto& c : system->constraints)
                    {
                        if (c.decl != valParam.getDecl())
                            continue;

                        auto cVal = c.val.As<IntVal>();
                        SLANG_RELEASE_ASSERT(cVal.Ptr());

                        if (!val)
                        {
                            val = cVal;
                        }
                        else
                        {
                            if(!val->EqualsVal(cVal.Ptr()))
                            {
                                // failure!
                                return SubstitutionSet();
                            }
                        }

                        c.satisfied = true;
                    }

                    if (!val)
                    {
                        // failure!
                        return SubstitutionSet();
                    }
                    args.Add(val);
                }
                else
                {
                    // ignore anything that isn't a generic parameter
                }
            }

            // After we've solved for the explicit arguments, we need to
            // make a second pass and consider the implicit arguments,
            // based on what we've already determined to be the values
            // for the explicit arguments.

            // Before we begin, we are going to go ahead and create the
            // "solved" substitution that we will return if everything works.
            // This is because we are going to use this substitution,
            // partially filled in with the results we know so far,
            // in order to specialize any constraints on the generic.
            //
            // E.g., if the generic parameters were `<T : ISidekick>`, and
            // we've already decided that `T` is `Robin`, then we want to
            // search for a conformance `Robin : ISidekick`, which involved
            // apply the substitutions we already know...

            RefPtr<GenericSubstitution> solvedSubst = new GenericSubstitution();
            solvedSubst->genericDecl = genericDeclRef.getDecl();
            solvedSubst->outer = genericDeclRef.substitutions.substitutions;
            solvedSubst->args = args;
            resultSubst.substitutions = solvedSubst;

            for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType<GenericTypeConstraintDecl>() )
            {
                DeclRef<GenericTypeConstraintDecl> constraintDeclRef(
                    constraintDecl,
                    solvedSubst);

                // Extract the (substituted) sub- and super-type from the constraint.
                auto sub = GetSub(constraintDeclRef);
                auto sup = GetSup(constraintDeclRef);

                // Search for a witness that shows the constraint is satisfied.
                auto subTypeWitness = tryGetSubtypeWitness(sub, sup);
                if(subTypeWitness)
                {
                    // We found a witness, so it will become an (implicit) argument.
                    solvedSubst->args.Add(subTypeWitness);
                }
                else
                {
                    // No witness was found, so the inference will now fail.
                    //
                    // TODO: Ideally we should print an error message in
                    // this case, to let the user know why things failed.
                    return SubstitutionSet();
                }

                // TODO: We may need to mark some constrains in our constraint
                // system as being solved now, as a result of the witness we found.
            }

            // Make sure we haven't constructed any spurious constraints
            // that we aren't able to satisfy:
            for (auto c : system->constraints)
            {
                if (!c.satisfied)
                {
                    return SubstitutionSet();
                }
            }

            return resultSubst;
        }


        // State related to overload resolution for a call
        // to an overloaded symbol
        struct OverloadResolveContext
        {
            enum class Mode
            {
                // We are just checking if a candidate works or not
                JustTrying,

                // We want to actually update the AST for a chosen candidate
                ForReal,
            };

            // Location to use when reporting overload-resolution errors.
            SourceLoc loc;

            // The original expression (if any) that triggered things
            RefPtr<Expr> originalExpr;

            // Source location of the "function" part of the expression, if any
            SourceLoc       funcLoc;

            // The original arguments to the call
            UInt argCount = 0;
            RefPtr<Expr>* args = nullptr;
            RefPtr<Type>* argTypes = nullptr;

            UInt getArgCount() { return argCount; }
            RefPtr<Expr>& getArg(UInt index) { return args[index]; }
            RefPtr<Type>& getArgType(UInt index)
            {
                if(argTypes)
                    return argTypes[index];
                else
                    return getArg(index)->type.type;
            }

            bool disallowNestedConversions = false;

            RefPtr<Expr> baseExpr;

            // Are we still trying out candidates, or are we
            // checking the chosen one for real?
            Mode mode = Mode::JustTrying;

            // We store one candidate directly, so that we don't
            // need to do dynamic allocation on the list every time
            OverloadCandidate bestCandidateStorage;
            OverloadCandidate*	bestCandidate = nullptr;

            // Full list of all candidates being considered, in the ambiguous case
            List<OverloadCandidate> bestCandidates;
        };

        struct ParamCounts
        {
            UInt required;
            UInt allowed;
        };

        // count the number of parameters required/allowed for a callable
        ParamCounts CountParameters(FilteredMemberRefList<ParamDecl> params)
        {
            ParamCounts counts = { 0, 0 };
            for (auto param : params)
            {
                counts.allowed++;

                // No initializer means no default value
                //
                // TODO(tfoley): The logic here is currently broken in two ways:
                //
                // 1. We are assuming that once one parameter has a default, then all do.
                //    This can/should be validated earlier, so that we can assume it here.
                //
                // 2. We are not handling the possibility of multiple declarations for
                //    a single function, where we'd need to merge default parameters across
                //    all the declarations.
                if (!param.getDecl()->initExpr)
                {
                    counts.required++;
                }
            }
            return counts;
        }

        // count the number of parameters required/allowed for a generic
        ParamCounts CountParameters(DeclRef<GenericDecl> genericRef)
        {
            ParamCounts counts = { 0, 0 };
            for (auto m : genericRef.getDecl()->Members)
            {
                if (auto typeParam = m.As<GenericTypeParamDecl>())
                {
                    counts.allowed++;
                    if (!typeParam->initType.Ptr())
                    {
                        counts.required++;
                    }
                }
                else if (auto valParam = m.As<GenericValueParamDecl>())
                {
                    counts.allowed++;
                    if (!valParam->initExpr)
                    {
                        counts.required++;
                    }
                }
            }
            return counts;
        }

        bool TryCheckOverloadCandidateArity(
            OverloadResolveContext&		context,
            OverloadCandidate const&	candidate)
        {
            UInt argCount = context.getArgCount();
            ParamCounts paramCounts = { 0, 0 };
            switch (candidate.flavor)
            {
            case OverloadCandidate::Flavor::Func:
                paramCounts = CountParameters(GetParameters(candidate.item.declRef.As<CallableDecl>()));
                break;

            case OverloadCandidate::Flavor::Generic:
                paramCounts = CountParameters(candidate.item.declRef.As<GenericDecl>());
                break;

            default:
                SLANG_UNEXPECTED("unknown flavor of overload candidate");
                break;
            }

            if (argCount >= paramCounts.required && argCount <= paramCounts.allowed)
                return true;

            // Emit an error message if we are checking this call for real
            if (context.mode != OverloadResolveContext::Mode::JustTrying)
            {
                if (argCount < paramCounts.required)
                {
                    getSink()->diagnose(context.loc, Diagnostics::notEnoughArguments, argCount, paramCounts.required);
                }
                else
                {
                    SLANG_ASSERT(argCount > paramCounts.allowed);
                    getSink()->diagnose(context.loc, Diagnostics::tooManyArguments, argCount, paramCounts.allowed);
                }
            }

            return false;
        }

        bool TryCheckOverloadCandidateFixity(
            OverloadResolveContext&		context,
            OverloadCandidate const&	candidate)
        {
            auto expr = context.originalExpr;

            auto decl = candidate.item.declRef.decl;

            if(auto prefixExpr = expr.As<PrefixExpr>())
            {
                if(decl->HasModifier<PrefixModifier>())
                    return true;

                if (context.mode != OverloadResolveContext::Mode::JustTrying)
                {
                    getSink()->diagnose(context.loc, Diagnostics::expectedPrefixOperator);
                    getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName());
                }

                return false;
            }
            else if(auto postfixExpr = expr.As<PostfixExpr>())
            {
                if(decl->HasModifier<PostfixModifier>())
                    return true;

                if (context.mode != OverloadResolveContext::Mode::JustTrying)
                {
                    getSink()->diagnose(context.loc, Diagnostics::expectedPostfixOperator);
                    getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName());
                }

                return false;
            }
            else
            {
                return true;
            }

            return false;
        }

        bool TryCheckGenericOverloadCandidateTypes(
            OverloadResolveContext&	context,
            OverloadCandidate&		candidate)
        {
            auto genericDeclRef = candidate.item.declRef.As<GenericDecl>();

            // We will go ahead and hang onto the arguments that we've
            // already checked, since downstream validation might need
            // them.
            auto genSubst = new GenericSubstitution();
            candidate.subst = genSubst;
            auto& checkedArgs = genSubst->args;

            int aa = 0;
            for (auto memberRef : getMembers(genericDeclRef))
            {
                if (auto typeParamRef = memberRef.As<GenericTypeParamDecl>())
                {
                    auto arg = context.getArg(aa++);

                    TypeExp typeExp;
                    if (context.mode == OverloadResolveContext::Mode::JustTrying)
                    {
                        typeExp = tryCoerceToProperType(TypeExp(arg));
                        if(!typeExp.type)
                        {
                            return false;
                        }
                    }
                    else
                    {
                        typeExp = CoerceToProperType(TypeExp(arg));
                    }
                    checkedArgs.Add(typeExp.type);
                }
                else if (auto valParamRef = memberRef.As<GenericValueParamDecl>())
                {
                    auto arg = context.getArg(aa++);

                    if (context.mode == OverloadResolveContext::Mode::JustTrying)
                    {
                        ConversionCost cost = kConversionCost_None;
                        if (!CanCoerce(GetType(valParamRef), arg->type, &cost))
                        {
                            return false;
                        }
                        candidate.conversionCostSum += cost;
                    }

                    arg = Coerce(GetType(valParamRef), arg);
                    auto val = ExtractGenericArgInteger(arg);
                    checkedArgs.Add(val);
                }
                else
                {
                    continue;
                }
            }

            // Okay, we've made it!
            return true;
        }

        bool TryCheckOverloadCandidateTypes(
            OverloadResolveContext&	context,
            OverloadCandidate&		candidate)
        {
            UInt argCount = context.getArgCount();

            List<DeclRef<ParamDecl>> params;
            switch (candidate.flavor)
            {
            case OverloadCandidate::Flavor::Func:
                params = GetParameters(candidate.item.declRef.As<CallableDecl>()).ToArray();
                break;

            case OverloadCandidate::Flavor::Generic:
                return TryCheckGenericOverloadCandidateTypes(context, candidate);

            default:
                SLANG_UNEXPECTED("unknown flavor of overload candidate");
                break;
            }

            // Note(tfoley): We might have fewer arguments than parameters in the
            // case where one or more parameters had defaults.
            SLANG_RELEASE_ASSERT(argCount <= params.Count());

            for (UInt ii = 0; ii < argCount; ++ii)
            {
                auto& arg = context.getArg(ii);
                auto argType = context.getArgType(ii);
                auto param = params[ii];

                if (context.mode == OverloadResolveContext::Mode::JustTrying)
                {
                    ConversionCost cost = kConversionCost_None;
                    if( context.disallowNestedConversions )
                    {
                        // We need an exact match in this case.
                        if(!GetType(param)->Equals(argType))
                            return false;
                    }
                    else if (!CanCoerce(GetType(param), argType, &cost))
                    {
                        return false;
                    }
                    candidate.conversionCostSum += cost;
                }
                else
                {
                    arg = Coerce(GetType(param), arg);
                }
            }
            return true;
        }

        bool TryCheckOverloadCandidateDirections(
            OverloadResolveContext&		/*context*/,
            OverloadCandidate const&	/*candidate*/)
        {
            // TODO(tfoley): check `in` and `out` markers, as needed.
            return true;
        }

        // Create a witness that attests to the fact that `type`
        // is equal to itself.
        RefPtr<Val> createTypeEqualityWitness(
            Type*  type)
        {
            RefPtr<TypeEqualityWitness> rs = new TypeEqualityWitness();
            rs->sub = type;
            rs->sup = type;
            return rs;
        }

        // If `sub` is a subtype of `sup`, then return a value that
        // can serve as a "witness" for that fact.
        RefPtr<Val> tryGetSubtypeWitness(
            RefPtr<Type>    sub,
            RefPtr<Type>    sup)
        {
            if(sub->Equals(sup))
            {
                // They are the same type, so we just need a witness
                // for type equality.
                return createTypeEqualityWitness(sub);
            }

            if(auto supDeclRefType = sup->As<DeclRefType>())
            {
                auto supDeclRef = supDeclRefType->declRef;
                if(auto supInterfaceDeclRef = supDeclRef.As<InterfaceDecl>())
                {
                    if(auto witness = tryGetInterfaceConformanceWitness(sub, supInterfaceDeclRef))
                    {
                        return witness;
                    }
                }
            }

            return nullptr;
        }

        // In the case where we are explicitly applying a generic
        // to arguments (e.g., `G<A,B>`) check that the constraints
        // on those parameters are satisfied.
        //
        // Note: the constraints actually work as additional parameters/arguments
        // of the generic, and so we need to reify them into the final
        // argument list.
        //
        bool TryCheckOverloadCandidateConstraints(
            OverloadResolveContext&		context,
            OverloadCandidate const&	candidate)
        {
            // We only need this step for generics, so always succeed on
            // everything else.
            if(candidate.flavor != OverloadCandidate::Flavor::Generic)
                return true;

            auto genericDeclRef = candidate.item.declRef.As<GenericDecl>();
            SLANG_ASSERT(genericDeclRef); // otherwise we wouldn't be a generic candidate...

            // We should have the existing arguments to the generic
            // handy, so that we can construct a substitution list.

            RefPtr<GenericSubstitution> subst = candidate.subst.As<GenericSubstitution>();
            SLANG_ASSERT(subst);

            subst->genericDecl = genericDeclRef.getDecl();
            subst->outer = genericDeclRef.substitutions.substitutions;

            for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType<GenericTypeConstraintDecl>() )
            {
                auto subset = genericDeclRef.substitutions;
                subset.substitutions = subst;
                DeclRef<GenericTypeConstraintDecl> constraintDeclRef(
                    constraintDecl, subset);

                auto sub = GetSub(constraintDeclRef);
                auto sup = GetSup(constraintDeclRef);

                auto subTypeWitness = tryGetSubtypeWitness(sub, sup);
                if(subTypeWitness)
                {
                    subst->args.Add(subTypeWitness);
                }
                else
                {
                    if(context.mode != OverloadResolveContext::Mode::JustTrying)
                    {
                        // TODO: diagnose a problem here
                        getSink()->diagnose(context.loc, Diagnostics::unimplemented, "generic constraint not satisfied");
                    }
                    return false;
                }
            }

            // Done checking all the constraints, hooray.
            return true;
        }

        // Try to check an overload candidate, but bail out
        // if any step fails
        void TryCheckOverloadCandidate(
            OverloadResolveContext&		context,
            OverloadCandidate&			candidate)
        {
            if (!TryCheckOverloadCandidateArity(context, candidate))
                return;

            candidate.status = OverloadCandidate::Status::ArityChecked;
            if (!TryCheckOverloadCandidateFixity(context, candidate))
                return;

            candidate.status = OverloadCandidate::Status::FixityChecked;
            if (!TryCheckOverloadCandidateTypes(context, candidate))
                return;

            candidate.status = OverloadCandidate::Status::TypeChecked;
            if (!TryCheckOverloadCandidateDirections(context, candidate))
                return;

            candidate.status = OverloadCandidate::Status::DirectionChecked;
            if (!TryCheckOverloadCandidateConstraints(context, candidate))
                return;

            candidate.status = OverloadCandidate::Status::Appicable;
        }

        // Create the representation of a given generic applied to some arguments
        RefPtr<Expr> createGenericDeclRef(
            RefPtr<Expr>            baseExpr,
            RefPtr<Expr>            originalExpr,
            RefPtr<GenericSubstitution>   subst)
        {
            auto baseDeclRefExpr = baseExpr.As<DeclRefExpr>();
            if (!baseDeclRefExpr)
            {
                SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration");
                return CreateErrorExpr(originalExpr);
            }
            auto baseGenericRef = baseDeclRefExpr->declRef.As<GenericDecl>();
            if (!baseGenericRef)
            {
                SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration");
                return CreateErrorExpr(originalExpr);
            }

            subst->genericDecl = baseGenericRef.getDecl();
            subst->outer = baseGenericRef.substitutions.substitutions;

            DeclRef<Decl> innerDeclRef(GetInner(baseGenericRef), subst);

            RefPtr<Expr> base;
            if (auto mbrExpr = baseExpr.As<MemberExpr>())
                base = mbrExpr->BaseExpression;

            return ConstructDeclRefExpr(
                innerDeclRef,
                base,
                originalExpr->loc);
        }

        // Take an overload candidate that previously got through
        // `TryCheckOverloadCandidate` above, and try to finish
        // up the work and turn it into a real expression.
        //
        // If the candidate isn't actually applicable, this is
        // where we'd start reporting the issue(s).
        RefPtr<Expr> CompleteOverloadCandidate(
            OverloadResolveContext&		context,
            OverloadCandidate&			candidate)
        {
            // special case for generic argument inference failure
            if (candidate.status == OverloadCandidate::Status::GenericArgumentInferenceFailed)
            {
                String callString = getCallSignatureString(context);
                getSink()->diagnose(
                    context.loc,
                    Diagnostics::genericArgumentInferenceFailed,
                    callString);

                String declString = getDeclSignatureString(candidate.item);
                getSink()->diagnose(candidate.item.declRef, Diagnostics::genericSignatureTried, declString);
                goto error;
            }

            context.mode = OverloadResolveContext::Mode::ForReal;

            if (!TryCheckOverloadCandidateArity(context, candidate))
                goto error;

            if (!TryCheckOverloadCandidateFixity(context, candidate))
                goto error;

            if (!TryCheckOverloadCandidateTypes(context, candidate))
                goto error;

            if (!TryCheckOverloadCandidateDirections(context, candidate))
                goto error;

            if (!TryCheckOverloadCandidateConstraints(context, candidate))
                goto error;

            {
                auto baseExpr = ConstructLookupResultExpr(
                    candidate.item, context.baseExpr, context.funcLoc);

                switch(candidate.flavor)
                {
                case OverloadCandidate::Flavor::Func:
                    {
                        RefPtr<AppExprBase> callExpr = context.originalExpr.As<InvokeExpr>();
                        if(!callExpr)
                        {
                            callExpr = new InvokeExpr();
                            callExpr->loc = context.loc;

                            for(UInt aa = 0; aa < context.argCount; ++aa)
                                callExpr->Arguments.Add(context.getArg(aa));
                        }


                        callExpr->FunctionExpr = baseExpr;
                        callExpr->type = QualType(candidate.resultType);

                        // A call may yield an l-value, and we should take a look at the candidate to be sure
                        if(auto subscriptDeclRef = candidate.item.declRef.As<SubscriptDecl>())
                        {
                            for(auto setter : subscriptDeclRef.getDecl()->getMembersOfType<SetterDecl>())
                            {
                                callExpr->type.IsLeftValue = true;
                            }
                            for(auto refAccessor : subscriptDeclRef.getDecl()->getMembersOfType<RefAccessorDecl>())
                            {
                                callExpr->type.IsLeftValue = true;
                            }
                        }

                        // TODO: there may be other cases that confer l-value-ness

                        return callExpr;
                    }

                    break;

                case OverloadCandidate::Flavor::Generic:
                    return createGenericDeclRef(
                        baseExpr,
                        context.originalExpr,
                        candidate.subst.As<GenericSubstitution>());
                    break;

                default:
                    SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "unknown overload candidate flavor");
                    break;
                }
            }


        error:

            if(context.originalExpr)
            {
                return CreateErrorExpr(context.originalExpr.Ptr());
            }
            else
            {
                SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "no original expression for overload result");
                return nullptr;
            }
        }

        // Implement a comparison operation between overload candidates,
        // so that the better candidate compares as less-than the other
        int CompareOverloadCandidates(
            OverloadCandidate*	left,
            OverloadCandidate*	right)
        {
            // If one candidate got further along in validation, pick it
            if (left->status != right->status)
                return int(right->status) - int(left->status);

            // If both candidates are applicable, then we need to compare
            // the costs of their type conversion sequences
            if(left->status == OverloadCandidate::Status::Appicable)
            {
                if (left->conversionCostSum != right->conversionCostSum)
                    return left->conversionCostSum - right->conversionCostSum;
            }

            return 0;
        }

        void AddOverloadCandidateInner(
            OverloadResolveContext& context,
            OverloadCandidate&		candidate)
        {
            // Filter our existing candidates, to remove any that are worse than our new one

            bool keepThisCandidate = true; // should this candidate be kept?

            if (context.bestCandidates.Count() != 0)
            {
                // We have multiple candidates right now, so filter them.
                bool anyFiltered = false;
                // Note that we are querying the list length on every iteration,
                // because we might remove things.
                for (UInt cc = 0; cc < context.bestCandidates.Count(); ++cc)
                {
                    int cmp = CompareOverloadCandidates(&candidate, &context.bestCandidates[cc]);
                    if (cmp < 0)
                    {
                        // our new candidate is better!

                        // remove it from the list (by swapping in a later one)
                        context.bestCandidates.FastRemoveAt(cc);
                        // and then reduce our index so that we re-visit the same index
                        --cc;

                        anyFiltered = true;
                    }
                    else if(cmp > 0)
                    {
                        // our candidate is worse!
                        keepThisCandidate = false;
                    }
                }
                // It should not be possible that we removed some existing candidate *and*
                // chose not to keep this candidate (otherwise the better-ness relation
                // isn't transitive). Therefore we confirm that we either chose to keep
                // this candidate (in which case filtering is okay), or we didn't filter
                // anything.
                SLANG_ASSERT(keepThisCandidate || !anyFiltered);
            }
            else if(context.bestCandidate)
            {
                // There's only one candidate so far
                int cmp = CompareOverloadCandidates(&candidate, context.bestCandidate);
                if(cmp < 0)
                {
                    // our new candidate is better!
                    context.bestCandidate = nullptr;
                }
                else if (cmp > 0)
                {
                    // our candidate is worse!
                    keepThisCandidate = false;
                }
            }

            // If our candidate isn't good enough, then drop it
            if (!keepThisCandidate)
                return;

            // Otherwise we want to keep the candidate
            if (context.bestCandidates.Count() > 0)
            {
                // There were already multiple candidates, and we are adding one more
                context.bestCandidates.Add(candidate);
            }
            else if (context.bestCandidate)
            {
                // There was a unique best candidate, but now we are ambiguous
                context.bestCandidates.Add(*context.bestCandidate);
                context.bestCandidates.Add(candidate);
                context.bestCandidate = nullptr;
            }
            else
            {
                // This is the only candidate worthe keeping track of right now
                context.bestCandidateStorage = candidate;
                context.bestCandidate = &context.bestCandidateStorage;
            }
        }

        void AddOverloadCandidate(
            OverloadResolveContext& context,
            OverloadCandidate&		candidate)
        {
            // Try the candidate out, to see if it is applicable at all.
            TryCheckOverloadCandidate(context, candidate);

            // Now (potentially) add it to the set of candidate overloads to consider.
            AddOverloadCandidateInner(context, candidate);
        }

        void AddFuncOverloadCandidate(
            LookupResultItem			item,
            DeclRef<CallableDecl>             funcDeclRef,
            OverloadResolveContext&		context)
        {
            auto funcDecl = funcDeclRef.getDecl();
            checkDecl(funcDecl);

            // If this function is a redeclaration,
            // then we don't want to include it multiple times,
            // and mistakenly think we have an ambiguous call.
            //
            // Instead, we will carefully consider only the
            // "primary" declaration of any callable.
            if (auto primaryDecl = funcDecl->primaryDecl)
            {
                if (funcDecl != primaryDecl)
                {
                    // This is a redeclaration, so we don't
                    // want to consider it. The primary
                    // declaration should also get considered
                    // for the call site and it will match
                    // anything this declaration would have
                    // matched.
                    return;
                }
            }

            OverloadCandidate candidate;
            candidate.flavor = OverloadCandidate::Flavor::Func;
            candidate.item = item;
            candidate.resultType = GetResultType(funcDeclRef);

            AddOverloadCandidate(context, candidate);
        }

        void AddFuncOverloadCandidate(
            RefPtr<FuncType>		/*funcType*/,
            OverloadResolveContext&	/*context*/)
        {
#if 0
            if (funcType->decl)
            {
                AddFuncOverloadCandidate(funcType->decl, context);
            }
            else if (funcType->Func)
            {
                AddFuncOverloadCandidate(funcType->Func->SyntaxNode, context);
            }
            else if (funcType->Component)
            {
                AddComponentFuncOverloadCandidate(funcType->Component, context);
            }
#else
            throw "unimplemented";
#endif
        }

        // Add a candidate callee for overload resolution, based on
        // calling a particular `ConstructorDecl`.
        void AddCtorOverloadCandidate(
            LookupResultItem            typeItem,
            RefPtr<Type>                type,
            DeclRef<ConstructorDecl>    ctorDeclRef,
            OverloadResolveContext&     context,
            RefPtr<Type>                resultType)
        {
            checkDecl(ctorDeclRef.getDecl());

            // `typeItem` refers to the type being constructed (the thing
            // that was applied as a function) so we need to construct
            // a `LookupResultItem` that refers to the constructor instead

            LookupResultItem ctorItem;
            ctorItem.declRef = ctorDeclRef;
            ctorItem.breadcrumbs = new LookupResultItem::Breadcrumb(
                LookupResultItem::Breadcrumb::Kind::Member,
                typeItem.declRef,
                typeItem.breadcrumbs);

            OverloadCandidate candidate;
            candidate.flavor = OverloadCandidate::Flavor::Func;
            candidate.item = ctorItem;
            candidate.resultType = resultType;

            AddOverloadCandidate(context, candidate);
        }

        // If the given declaration has generic parameters, then
        // return the corresponding `GenericDecl` that holds the
        // parameters, etc.
        GenericDecl* GetOuterGeneric(Decl* decl)
        {
            auto parentDecl = decl->ParentDecl;
            if (!parentDecl) return nullptr;
            auto parentGeneric = dynamic_cast<GenericDecl*>(parentDecl);
            return parentGeneric;
        }

        // Try to find a unification for two values
        bool TryUnifyVals(
            ConstraintSystem&	constraints,
            RefPtr<Val>			fst,
            RefPtr<Val>			snd)
        {
            // if both values are types, then unify types
            if (auto fstType = fst.As<Type>())
            {
                if (auto sndType = snd.As<Type>())
                {
                    return TryUnifyTypes(constraints, fstType, sndType);
                }
            }

            // if both values are constant integers, then compare them
            if (auto fstIntVal = fst.As<ConstantIntVal>())
            {
                if (auto sndIntVal = snd.As<ConstantIntVal>())
                {
                    return fstIntVal->value == sndIntVal->value;
                }
            }

            // Check if both are integer values in general
            if (auto fstInt = fst.As<IntVal>())
            {
                if (auto sndInt = snd.As<IntVal>())
                {
                    auto fstParam = fstInt.As<GenericParamIntVal>();
                    auto sndParam = sndInt.As<GenericParamIntVal>();

                    bool okay = false;
                    if (fstParam)
                    {
                        if(TryUnifyIntParam(constraints, fstParam->declRef, sndInt))
                            okay = true;
                    }
                    if (sndParam)
                    {
                        if(TryUnifyIntParam(constraints, sndParam->declRef, fstInt))
                            okay = true;
                    }
                    return okay;
                }
            }

            if (auto fstWit = fst.As<DeclaredSubtypeWitness>())
            {
                if (auto sndWit = snd.As<DeclaredSubtypeWitness>())
                {
                    auto constraintDecl1 = fstWit->declRef.As<TypeConstraintDecl>();
                    auto constraintDecl2 = sndWit->declRef.As<TypeConstraintDecl>();
                    SLANG_ASSERT(constraintDecl1);
                    SLANG_ASSERT(constraintDecl2);
                    return TryUnifyTypes(constraints,
                        constraintDecl1.getDecl()->getSup().type,
                        constraintDecl2.getDecl()->getSup().type);
                }
            }

            SLANG_UNIMPLEMENTED_X("value unification case");

            // default: fail
            return false;
        }

        bool tryUnifySubstitutions(
            ConstraintSystem&       constraints,
            RefPtr<Substitutions>   fst,
            RefPtr<Substitutions>   snd)
        {
            // They must both be NULL or non-NULL
            if (!fst || !snd)
                return !fst && !snd;

            if(auto fstGeneric = fst.As<GenericSubstitution>())
            {
                if(auto sndGeneric = snd.As<GenericSubstitution>())
                {
                    return tryUnifyGenericSubstitutions(
                        constraints,
                        fstGeneric,
                        sndGeneric);
                }
            }

            // TODO: need to handle other cases here

            return false;
        }

        bool tryUnifyGenericSubstitutions(
            ConstraintSystem&           constraints,
            RefPtr<GenericSubstitution> fst,
            RefPtr<GenericSubstitution> snd)
        {
            SLANG_ASSERT(fst);
            SLANG_ASSERT(snd);

            auto fstGen = fst;
            auto sndGen = snd;
            // They must be specializing the same generic
            if (fstGen->genericDecl != sndGen->genericDecl)
                return false;

            // Their arguments must unify
            SLANG_RELEASE_ASSERT(fstGen->args.Count() == sndGen->args.Count());
            UInt argCount = fstGen->args.Count();
            bool okay = true;
            for (UInt aa = 0; aa < argCount; ++aa)
            {
                if (!TryUnifyVals(constraints, fstGen->args[aa], sndGen->args[aa]))
                {
                    okay = false;
                }
            }

            // Their "base" specializations must unify
            if (!tryUnifySubstitutions(constraints, fstGen->outer, sndGen->outer))
            {
                okay = false;
            }

            return okay;
        }

        bool TryUnifyTypeParam(
            ConstraintSystem&				constraints,
            RefPtr<GenericTypeParamDecl>	typeParamDecl,
            RefPtr<Type>			type)
        {
            // We want to constrain the given type parameter
            // to equal the given type.
            Constraint constraint;
            constraint.decl = typeParamDecl.Ptr();
            constraint.val = type;

            constraints.constraints.Add(constraint);

            return true;
        }

        bool TryUnifyIntParam(
            ConstraintSystem&               constraints,
            RefPtr<GenericValueParamDecl>	paramDecl,
            RefPtr<IntVal>                  val)
        {
            // We only want to accumulate constraints on
            // the parameters of the declarations being
            // specialized (don't accidentially constrain
            // parameters of a generic function based on
            // calls in its body).
            if(paramDecl->ParentDecl != constraints.genericDecl)
                return false;

            // We want to constrain the given parameter to equal the given value.
            Constraint constraint;
            constraint.decl = paramDecl.Ptr();
            constraint.val = val;

            constraints.constraints.Add(constraint);

            return true;
        }

        bool TryUnifyIntParam(
            ConstraintSystem&       constraints,
            DeclRef<VarDeclBase> const&   varRef,
            RefPtr<IntVal>          val)
        {
            if(auto genericValueParamRef = varRef.As<GenericValueParamDecl>())
            {
                return TryUnifyIntParam(constraints, RefPtr<GenericValueParamDecl>(genericValueParamRef.getDecl()), val);
            }
            else
            {
                return false;
            }
        }

        bool TryUnifyTypesByStructuralMatch(
            ConstraintSystem&       constraints,
            RefPtr<Type>  fst,
            RefPtr<Type>  snd)
        {
            if (auto fstDeclRefType = fst->As<DeclRefType>())
            {
                auto fstDeclRef = fstDeclRefType->declRef;

                if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(fstDeclRef.getDecl()))
                    return TryUnifyTypeParam(constraints, typeParamDecl, snd);

                if (auto sndDeclRefType = snd->As<DeclRefType>())
                {
                    auto sndDeclRef = sndDeclRefType->declRef;

                    if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(sndDeclRef.getDecl()))
                        return TryUnifyTypeParam(constraints, typeParamDecl, fst);

                    // can't be unified if they refer to differnt declarations.
                    if (fstDeclRef.getDecl() != sndDeclRef.getDecl()) return false;

                    // next we need to unify the substitutions applied
                    // to each decalration reference.
                    if (!tryUnifySubstitutions(
                        constraints,
                        fstDeclRef.substitutions.substitutions,
                        sndDeclRef.substitutions.substitutions))
                    {
                        return false;
                    }

                    return true;
                }
            }

            return false;
        }

        bool TryUnifyTypes(
            ConstraintSystem&       constraints,
            RefPtr<Type>  fst,
            RefPtr<Type>  snd)
        {
            if (fst->Equals(snd)) return true;

            // An error type can unify with anything, just so we avoid cascading errors.

            if (auto fstErrorType = fst->As<ErrorType>())
                return true;

            if (auto sndErrorType = snd->As<ErrorType>())
                return true;

            // A generic parameter type can unify with anything.
            // TODO: there actually needs to be some kind of "occurs check" sort
            // of thing here...

            if (auto fstDeclRefType = fst->As<DeclRefType>())
            {
                auto fstDeclRef = fstDeclRefType->declRef;

                if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(fstDeclRef.getDecl()))
                {
                    if(typeParamDecl->ParentDecl == constraints.genericDecl )
                        return TryUnifyTypeParam(constraints, typeParamDecl, snd);
                }
            }

            if (auto sndDeclRefType = snd->As<DeclRefType>())
            {
                auto sndDeclRef = sndDeclRefType->declRef;

                if (auto typeParamDecl = dynamic_cast<GenericTypeParamDecl*>(sndDeclRef.getDecl()))
                {
                    if(typeParamDecl->ParentDecl == constraints.genericDecl )
                        return TryUnifyTypeParam(constraints, typeParamDecl, fst);
                }
            }

            // If we can unify the types structurally, then we are golden
            if(TryUnifyTypesByStructuralMatch(constraints, fst, snd))
                return true;

            // Now we need to consider cases where coercion might
            // need to be applied. For now we can try to do this
            // in a completely ad hoc fashion, but eventually we'd
            // want to do it more formally.

            if(auto fstVectorType = fst->As<VectorExpressionType>())
            {
                if(auto sndScalarType = snd->As<BasicExpressionType>())
                {
                    return TryUnifyTypes(
                        constraints,
                        fstVectorType->elementType,
                        sndScalarType);
                }
            }

            if(auto fstScalarType = fst->As<BasicExpressionType>())
            {
                if(auto sndVectorType = snd->As<VectorExpressionType>())
                {
                    return TryUnifyTypes(
                        constraints,
                        fstScalarType,
                        sndVectorType->elementType);
                }
            }

            // TODO: the same thing for vectors...

            return false;
        }

        // Is the candidate extension declaration actually applicable to the given type
        DeclRef<ExtensionDecl> ApplyExtensionToType(
            ExtensionDecl*  extDecl,
            RefPtr<Type>    type)
        {
            DeclRef<ExtensionDecl> extDeclRef = makeDeclRef(extDecl);

            // If the extension is a generic extension, then we
            // need to infer type argumenst that will give
            // us a target type that matches `type`.
            //
            if (auto extGenericDecl = GetOuterGeneric(extDecl))
            {
                ConstraintSystem constraints;
                constraints.loc = extDecl->loc;
                constraints.genericDecl = extGenericDecl;

                if (!TryUnifyTypes(constraints, extDecl->targetType.Ptr(), type))
                    return DeclRef<ExtensionDecl>();

                auto constraintSubst = TrySolveConstraintSystem(&constraints, DeclRef<Decl>(extGenericDecl, nullptr).As<GenericDecl>());
                if (!constraintSubst)
                {
                    return DeclRef<ExtensionDecl>();
                }

                // Consruct a reference to the extension with our constraint variables
                // set as they were found by solving the constraint system.
                extDeclRef = DeclRef<Decl>(extDecl, constraintSubst).As<ExtensionDecl>();
            }

            // Now extract the target type from our (possibly specialized) extension decl-ref.
            RefPtr<Type> targetType = GetTargetType(extDeclRef);

            // As a bit of a kludge here, if the target type of the extension is
            // an interface, and the `type` we are trying to match up has a this-type
            // substitution for that interface, then we want to attach a matching
            // substitution to the extension decl-ref.
            if(auto targetDeclRefType = targetType->As<DeclRefType>())
            {
                if(auto targetInterfaceDeclRef = targetDeclRefType->declRef.As<InterfaceDecl>())
                {
                    // Okay, the target type is an interface.
                    //
                    // Is the type we want to apply to also an interface?
                    if(auto appDeclRefType = type->As<DeclRefType>())
                    {
                        if(auto appInterfaceDeclRef = appDeclRefType->declRef.As<InterfaceDecl>())
                        {
                            if(appInterfaceDeclRef.getDecl() == targetInterfaceDeclRef.getDecl())
                            {
                                // Looks like we have a match in the types,
                                // now let's see if we have a this-type substitution.
                                if(auto appThisTypeSubst = appInterfaceDeclRef.substitutions.substitutions.As<ThisTypeSubstitution>())
                                {
                                    if(appThisTypeSubst->interfaceDecl == appInterfaceDeclRef.getDecl())
                                    {
                                        // The type we want to apply to has a this-type substitution,
                                        // and (by construction) the target type currently does not.
                                        //
                                        SLANG_ASSERT(!targetInterfaceDeclRef.substitutions.substitutions.As<ThisTypeSubstitution>());

                                        // We will create a new substitution to apply to the target type.
                                        RefPtr<ThisTypeSubstitution> newTargetSubst = new ThisTypeSubstitution();
                                        newTargetSubst->interfaceDecl = appThisTypeSubst->interfaceDecl;
                                        newTargetSubst->witness = appThisTypeSubst->witness;
                                        newTargetSubst->outer = targetInterfaceDeclRef.substitutions.substitutions;

                                        targetType = DeclRefType::Create(getSession(),
                                            DeclRef<InterfaceDecl>(targetInterfaceDeclRef.getDecl(), newTargetSubst));

                                        // Note: we are constructing a this-type substitution that
                                        // we will apply to the extension declaration as well.
                                        // This is not strictly allowed by our current representation
                                        // choices, but we need it in order to make sure that
                                        // references to the target type of the extension
                                        // declaration have a chance to resolve the way we want them to.

                                        RefPtr<ThisTypeSubstitution> newExtSubst = new ThisTypeSubstitution();
                                        newExtSubst->interfaceDecl = appThisTypeSubst->interfaceDecl;
                                        newExtSubst->witness = appThisTypeSubst->witness;
                                        newExtSubst->outer = extDeclRef.substitutions.substitutions;

                                        extDeclRef = DeclRef<ExtensionDecl>(
                                            extDeclRef.getDecl(),
                                            newExtSubst);

                                        // TODO: Ideally we should also apply the chosen specialization to
                                        // the decl-ref for the extension, so that subsequent lookup through
                                        // the members of this extension will retain that substitution and
                                        // be able to apply it.
                                        //
                                        // E.g., if an extension method returns a value of an associated
                                        // type, then we'd want that to become specialized to a concrete
                                        // type when using the extension method on a value of concrete type.
                                        //
                                        // The challenge here that makes me reluctant to just staple on
                                        // such a substitution is that it wouldn't follow our implicit
                                        // rules about where `ThisTypeSubstitution`s can appear.
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // In order for this extension to apply to the given type, we
            // need to have a match on the target types.
            if (!type->Equals(targetType))
                return DeclRef<ExtensionDecl>();


            return extDeclRef;
        }

#if 0
        bool TryUnifyArgAndParamTypes(
            ConstraintSystem&				system,
            RefPtr<Expr>	argExpr,
            DeclRef<ParamDecl>					paramDeclRef)
        {
            // TODO(tfoley): potentially need a bit more
            // nuance in case where argument might be
            // an overload group...
            return TryUnifyTypes(system, argExpr->type, GetType(paramDeclRef));
        }
#endif

        // Take a generic declaration and try to specialize its parameters
        // so that the resulting inner declaration can be applicable in
        // a particular context...
        DeclRef<Decl> SpecializeGenericForOverload(
            DeclRef<GenericDecl>    genericDeclRef,
            OverloadResolveContext& context)
        {
            checkDecl(genericDeclRef.getDecl());

            ConstraintSystem constraints;
            constraints.loc = context.loc;
            constraints.genericDecl = genericDeclRef.getDecl();

            // Construct a reference to the inner declaration that has any generic
            // parameter substitutions in place already, but *not* any substutions
            // for the generic declaration we are currently trying to infer.
            auto innerDecl = GetInner(genericDeclRef);
            DeclRef<Decl> unspecializedInnerRef = DeclRef<Decl>(innerDecl, genericDeclRef.substitutions);

            // Check what type of declaration we are dealing with, and then try
            // to match it up with the arguments accordingly...
            if (auto funcDeclRef = unspecializedInnerRef.As<CallableDecl>())
            {
                auto params = GetParameters(funcDeclRef).ToArray();

                UInt argCount = context.getArgCount();
                UInt paramCount = params.Count();

                // Bail out on mismatch.
                // TODO(tfoley): need more nuance here
                if (argCount != paramCount)
                {
                    return DeclRef<Decl>(nullptr, nullptr);
                }

                for (UInt aa = 0; aa < argCount; ++aa)
                {
#if 0
                    if (!TryUnifyArgAndParamTypes(constraints, args[aa], params[aa]))
                        return DeclRef<Decl>(nullptr, nullptr);
#else
                    // The question here is whether failure to "unify" an argument
                    // and parameter should lead to immediate failure.
                    //
                    // The case that is interesting is if we want to unify, say:
                    // `vector<float,N>` and `vector<int,3>`
                    //
                    // It is clear that we should solve with `N = 3`, and then
                    // a later step may find that the resulting types aren't
                    // actually a match.
                    //
                    // A more refined approach to "unification" could of course
                    // see that `int` can convert to `float` and use that fact.
                    // (and indeed we already use something like this to unify
                    // `float` and `vector<T,3>`)
                    //
                    // So the question is then whether a mismatch during the
                    // unification step should be taken as an immediate failure...

                    TryUnifyTypes(constraints, context.getArgType(aa), GetType(params[aa]));
#endif
                }
            }
            else
            {
                // TODO(tfoley): any other cases needed here?
                return DeclRef<Decl>(nullptr, nullptr);
            }

            auto constraintSubst = TrySolveConstraintSystem(&constraints, genericDeclRef);
            if (!constraintSubst)
            {
                // constraint solving failed
                return DeclRef<Decl>(nullptr, nullptr);
            }

            // We can now construct a reference to the inner declaration using
            // the solution to our constraints.
            return DeclRef<Decl>(innerDecl, constraintSubst);
        }

        void AddAggTypeOverloadCandidates(
            LookupResultItem        typeItem,
            RefPtr<Type>            type,
            DeclRef<AggTypeDecl>    aggTypeDeclRef,
            OverloadResolveContext& context,
            RefPtr<Type>            resultType)
        {
            for (auto ctorDeclRef : getMembersOfType<ConstructorDecl>(aggTypeDeclRef))
            {
                // now work through this candidate...
                AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType);
            }

            // Also check for generic constructors.
            //
            // TODO: There is way too much duplication between this case and the extension
            // handling below, and all of this is *also* duplicative with the ordinary
            // overload resolution logic for function.
            //
            // The right solution is to handle a "constructor" call expression by
            // first doing member lookup in the type (for initializer members, which
            // should all share a common name), and then to do overload resolution using
            // the (possibly overloaded) result of that lookup.
            //
            for (auto genericDeclRef : getMembersOfType<GenericDecl>(aggTypeDeclRef))
            {
                if (auto ctorDecl = genericDeclRef.getDecl()->inner.As<ConstructorDecl>())
                {
                    DeclRef<Decl> innerRef = SpecializeGenericForOverload(genericDeclRef, context);
                    if (!innerRef)
                        continue;

                    DeclRef<ConstructorDecl> innerCtorRef = innerRef.As<ConstructorDecl>();
                    AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType);
                }
            }

            // Now walk through any extensions we can find for this types
            for (auto ext = GetCandidateExtensions(aggTypeDeclRef); ext; ext = ext->nextCandidateExtension)
            {
                auto extDeclRef = ApplyExtensionToType(ext, type);
                if (!extDeclRef)
                    continue;

                for (auto ctorDeclRef : getMembersOfType<ConstructorDecl>(extDeclRef))
                {
                    // TODO(tfoley): `typeItem` here should really reference the extension...

                    // now work through this candidate...
                    AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType);
                }

                // Also check for generic constructors
                for (auto genericDeclRef : getMembersOfType<GenericDecl>(extDeclRef))
                {
                    if (auto ctorDecl = genericDeclRef.getDecl()->inner.As<ConstructorDecl>())
                    {
                        DeclRef<Decl> innerRef = SpecializeGenericForOverload(genericDeclRef, context);
                        if (!innerRef)
                            continue;

                        DeclRef<ConstructorDecl> innerCtorRef = innerRef.As<ConstructorDecl>();

                        AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType);

                        // TODO(tfoley): need a way to do the solving step for the constraint system
                    }
                }
            }
        }

        void addGenericTypeParamOverloadCandidates(
            DeclRef<GenericTypeParamDecl>   typeDeclRef,
            OverloadResolveContext&         context,
            RefPtr<Type>                    resultType)
        {
            // We need to look for any constraints placed on the generic
            // type parameter, since they will give us information on
            // interfaces that the type must conform to.

            // We expect the parent of the generic type parameter to be a generic...
            auto genericDeclRef = typeDeclRef.GetParent().As<GenericDecl>();
            SLANG_ASSERT(genericDeclRef);

            for(auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef))
            {
                // Does this constraint pertain to the type we are working on?
                //
                // We want constraints of the form `T : Foo` where `T` is the
                // generic parameter in question, and `Foo` is whatever we are
                // constraining it to.
                auto subType = GetSub(constraintDeclRef);
                auto subDeclRefType = subType->As<DeclRefType>();
                if(!subDeclRefType)
                    continue;
                if(!subDeclRefType->declRef.Equals(typeDeclRef))
                    continue;

                // The super-type in the constraint (e.g., `Foo` in `T : Foo`)
                // will tell us a type we should use for lookup.
                auto bound = GetSup(constraintDeclRef);

                // Go ahead and use the target type:
                //
                // TODO: Need to consider case where this might recurse infinitely.
                AddTypeOverloadCandidates(bound, context, resultType);
            }
        }

        void AddTypeOverloadCandidates(
            RefPtr<Type>	        type,
            OverloadResolveContext&	context,
            RefPtr<Type>            resultType)
        {
            if (auto declRefType = type->As<DeclRefType>())
            {
                auto declRef = declRefType->declRef;
                if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>())
                {
                    AddAggTypeOverloadCandidates(LookupResultItem(aggTypeDeclRef), type, aggTypeDeclRef, context, resultType);
                }
                else if(auto genericTypeParamDeclRef = declRef.As<GenericTypeParamDecl>())
                {
                    addGenericTypeParamOverloadCandidates(
                        genericTypeParamDeclRef,
                        context,
                        resultType);
                }
            }
        }

        void AddDeclRefOverloadCandidates(
            LookupResultItem		item,
            OverloadResolveContext&	context)
        {
            auto declRef = item.declRef;

            if (auto funcDeclRef = item.declRef.As<CallableDecl>())
            {
                AddFuncOverloadCandidate(item, funcDeclRef, context);
            }
            else if (auto aggTypeDeclRef = item.declRef.As<AggTypeDecl>())
            {
                auto type = DeclRefType::Create(
                    getSession(),
                    aggTypeDeclRef);
                AddAggTypeOverloadCandidates(item, type, aggTypeDeclRef, context, type);
            }
            else if (auto genericDeclRef = item.declRef.As<GenericDecl>())
            {
                // Try to infer generic arguments, based on the context
                DeclRef<Decl> innerRef = SpecializeGenericForOverload(genericDeclRef, context);

                if (innerRef)
                {
                    // If inference works, then we've now got a
                    // specialized declaration reference we can apply.

                    LookupResultItem innerItem;
                    innerItem.breadcrumbs = item.breadcrumbs;
                    innerItem.declRef = innerRef;

                    AddDeclRefOverloadCandidates(innerItem, context);
                }
                else
                {
                    // If inference failed, then we need to create
                    // a candidate that can be used to reflect that fact
                    // (so we can report a good error)
                    OverloadCandidate candidate;
                    candidate.item = item;
                    candidate.flavor = OverloadCandidate::Flavor::UnspecializedGeneric;
                    candidate.status = OverloadCandidate::Status::GenericArgumentInferenceFailed;

                    AddOverloadCandidateInner(context, candidate);
                }
            }
            else if( auto typeDefDeclRef = item.declRef.As<TypeDefDecl>() )
            {
                auto type = getNamedType(getSession(), typeDefDeclRef);
                AddTypeOverloadCandidates(GetType(typeDefDeclRef), context, type);
            }
            else if( auto genericTypeParamDeclRef = item.declRef.As<GenericTypeParamDecl>() )
            {
                auto type = DeclRefType::Create(
                    getSession(),
                    genericTypeParamDeclRef);
                addGenericTypeParamOverloadCandidates(genericTypeParamDeclRef, context, type);
            }
            else
            {
                // TODO(tfoley): any other cases needed here?
            }
        }

        void AddOverloadCandidates(
            RefPtr<Expr>	funcExpr,
            OverloadResolveContext&			context)
        {
            auto funcExprType = funcExpr->type;

            if (auto declRefExpr = funcExpr.As<DeclRefExpr>())
            {
                // The expression directly referenced a declaration,
                // so we can use that declaration directly to look
                // for anything applicable.
                AddDeclRefOverloadCandidates(LookupResultItem(declRefExpr->declRef), context);
            }
            else if (auto funcType = funcExprType->As<FuncType>())
            {
                // TODO(tfoley): deprecate this path...
                AddFuncOverloadCandidate(funcType, context);
            }
            else if (auto overloadedExpr = funcExpr.As<OverloadedExpr>())
            {
                auto lookupResult = overloadedExpr->lookupResult2;
                SLANG_RELEASE_ASSERT(lookupResult.isOverloaded());
                for(auto item : lookupResult.items)
                {
                    AddDeclRefOverloadCandidates(item, context);
                }
            }
            else if (auto overloadedExpr2 = funcExpr.As<OverloadedExpr2>())
            {
                for (auto item : overloadedExpr2->candidiateExprs)
                {
                    AddOverloadCandidates(item, context);
                }
            }
            else if (auto typeType = funcExprType->As<TypeType>())
            {
                // If none of the above cases matched, but we are
                // looking at a type, then I suppose we have
                // a constructor call on our hands.
                //
                // TODO(tfoley): are there any meaningful types left
                // that aren't declaration references?
                auto type = typeType->type;
                AddTypeOverloadCandidates(type, context, type);
                return;
            }
        }

        void formatType(StringBuilder& sb, RefPtr<Type> type)
        {
            sb << type->ToString();
        }

        void formatVal(StringBuilder& sb, RefPtr<Val> val)
        {
            sb << val->ToString();
        }

        void formatDeclPath(StringBuilder& sb, DeclRef<Decl> declRef)
        {
            // Find the parent declaration
            auto parentDeclRef = declRef.GetParent();

            // If the immediate parent is a generic, then we probably
            // want the declaration above that...
            auto parentGenericDeclRef = parentDeclRef.As<GenericDecl>();
            if(parentGenericDeclRef)
            {
                parentDeclRef = parentGenericDeclRef.GetParent();
            }

            // Depending on what the parent is, we may want to format things specially
            if(auto aggTypeDeclRef = parentDeclRef.As<AggTypeDecl>())
            {
                formatDeclPath(sb, aggTypeDeclRef);
                sb << ".";
            }

            sb << getText(declRef.GetName());

            // If the parent declaration is a generic, then we need to print out its
            // signature
            if( parentGenericDeclRef )
            {
                auto genSubst = declRef.substitutions.substitutions.As<GenericSubstitution>();
                SLANG_RELEASE_ASSERT(genSubst);
                SLANG_RELEASE_ASSERT(genSubst->genericDecl == parentGenericDeclRef.getDecl());

                sb << "<";
                bool first = true;
                for(auto arg : genSubst->args)
                {
                    if(!first) sb << ", ";
                    formatVal(sb, arg);
                    first = false;
                }
                sb << ">";
            }
        }

        void formatDeclParams(StringBuilder& sb, DeclRef<Decl> declRef)
        {
            if (auto funcDeclRef = declRef.As<CallableDecl>())
            {

                // This is something callable, so we need to also print parameter types for overloading
                sb << "(";

                bool first = true;
                for (auto paramDeclRef : GetParameters(funcDeclRef))
                {
                    if (!first) sb << ", ";

                    formatType(sb, GetType(paramDeclRef));

                    first = false;

                }

                sb << ")";
            }
            else if(auto genericDeclRef = declRef.As<GenericDecl>())
            {
                sb << "<";
                bool first = true;
                for (auto paramDeclRef : getMembers(genericDeclRef))
                {
                    if(auto genericTypeParam = paramDeclRef.As<GenericTypeParamDecl>())
                    {
                        if (!first) sb << ", ";
                        first = false;

                        sb << getText(genericTypeParam.GetName());
                    }
                    else if(auto genericValParam = paramDeclRef.As<GenericValueParamDecl>())
                    {
                        if (!first) sb << ", ";
                        first = false;

                        formatType(sb, GetType(genericValParam));
                        sb << " ";
                        sb << getText(genericValParam.GetName());
                    }
                    else
                    {}
                }
                sb << ">";

                formatDeclParams(sb, DeclRef<Decl>(GetInner(genericDeclRef), genericDeclRef.substitutions));
            }
            else
            {
            }
        }

        void formatDeclSignature(StringBuilder& sb, DeclRef<Decl> declRef)
        {
            formatDeclPath(sb, declRef);
            formatDeclParams(sb, declRef);
        }

        String getDeclSignatureString(DeclRef<Decl> declRef)
        {
            StringBuilder sb;
            formatDeclSignature(sb, declRef);
            return sb.ProduceString();
        }

        String getDeclSignatureString(LookupResultItem item)
        {
            return getDeclSignatureString(item.declRef);
        }

        String getCallSignatureString(
            OverloadResolveContext&     context)
        {
            StringBuilder argsListBuilder;
            argsListBuilder << "(";

            UInt argCount = context.getArgCount();
            for( UInt aa = 0; aa < argCount; ++aa )
            {
                if(aa != 0) argsListBuilder << ", ";
                argsListBuilder << context.getArgType(aa)->ToString();
            }
            argsListBuilder << ")";
            return argsListBuilder.ProduceString();
        }

#if 0
        String GetCallSignatureString(RefPtr<AppExprBase> expr)
        {
            return getCallSignatureString(expr->Arguments);
        }
#endif

        RefPtr<Expr> ResolveInvoke(InvokeExpr * expr)
        {
            OverloadResolveContext context;
            // check if this is a stdlib operator call, if so we want to use cached results
            // to speed up compilation
            bool shouldAddToCache = false;
            OperatorOverloadCacheKey key;
            TypeCheckingCache* typeCheckingCache = getSession()->getTypeCheckingCache();
            if (auto opExpr = expr->As<OperatorExpr>())
            {
                if (key.fromOperatorExpr(opExpr))
                {
                    OverloadCandidate candidate;
                    if (typeCheckingCache->resolvedOperatorOverloadCache.TryGetValue(key, candidate))
                    {
                        context.bestCandidateStorage = candidate;
                        context.bestCandidate = &context.bestCandidateStorage;
                    }
                    else
                    {
                        shouldAddToCache = true;
                    }
                }
            }

            // Look at the base expression for the call, and figure out how to invoke it.
            auto funcExpr = expr->FunctionExpr;
            auto funcExprType = funcExpr->type;

            // If we are trying to apply an erroroneous expression, then just bail out now.
            if(IsErrorExpr(funcExpr))
            {
                return CreateErrorExpr(expr);
            }
            // If any of the arguments is an error, then we should bail out, to avoid
            // cascading errors where we successfully pick an overload, but not the one
            // the user meant.
            for (auto arg : expr->Arguments)
            {
                if (IsErrorExpr(arg))
                    return CreateErrorExpr(expr);
            }

            context.originalExpr = expr;
            context.funcLoc = funcExpr->loc;

            context.argCount = expr->Arguments.Count();
            context.args = expr->Arguments.Buffer();
            context.loc = expr->loc;

            if (auto funcMemberExpr = funcExpr.As<MemberExpr>())
            {
                context.baseExpr = funcMemberExpr->BaseExpression;
            }
            else if (auto funcOverloadExpr = funcExpr.As<OverloadedExpr>())
            {
                context.baseExpr = funcOverloadExpr->base;
            }
            else if (auto funcOverloadExpr2 = funcExpr.As<OverloadedExpr2>())
            {
                context.baseExpr = funcOverloadExpr2->base;
            }

            if (!context.bestCandidate)
            {
                AddOverloadCandidates(funcExpr, context);
            }

            if (context.bestCandidates.Count() > 0)
            {
                // Things were ambiguous.

                // It might be that things were only ambiguous because
                // one of the argument expressions had an error, and
                // so a bunch of candidates could match at that position.
                //
                // If any argument was an error, we skip out on printing
                // another message, to avoid cascading errors.
                for (auto arg : expr->Arguments)
                {
                    if (IsErrorExpr(arg))
                    {
                        return CreateErrorExpr(expr);
                    }
                }

                Name* funcName = nullptr;
                if (auto baseVar = funcExpr.As<VarExpr>())
                    funcName = baseVar->name;
                else if(auto baseMemberRef = funcExpr.As<MemberExpr>())
                    funcName = baseMemberRef->name;

                String argsList = getCallSignatureString(context);

                if (context.bestCandidates[0].status != OverloadCandidate::Status::Appicable)
                {
                    // There were multple equally-good candidates, but none actually usable.
                    // We will construct a diagnostic message to help out.

                    if (funcName)
                    {
                        getSink()->diagnose(expr, Diagnostics::noApplicableOverloadForNameWithArgs, funcName, argsList);
                    }
                    else
                    {
                        getSink()->diagnose(expr, Diagnostics::noApplicableWithArgs, argsList);
                    }
                }
                else
                {
                    // There were multiple applicable candidates, so we need to report them.

                    if (funcName)
                    {
                        getSink()->diagnose(expr, Diagnostics::ambiguousOverloadForNameWithArgs, funcName, argsList);
                    }
                    else
                    {
                        getSink()->diagnose(expr, Diagnostics::ambiguousOverloadWithArgs, argsList);
                    }
                }

                {
                    UInt candidateCount = context.bestCandidates.Count();
                    UInt maxCandidatesToPrint = 10; // don't show too many candidates at once...
                    UInt candidateIndex = 0;
                    for (auto candidate : context.bestCandidates)
                    {
                        String declString = getDeclSignatureString(candidate.item);

//                        declString = declString + "[" + String(candidate.conversionCostSum) + "]";

#if 0
                        // Debugging: ensure that we don't consider multiple declarations of the same operation
                        if (auto decl = dynamic_cast<CallableDecl*>(candidate.item.declRef.decl))
                        {
                            char buffer[1024];
                            sprintf_s(buffer, sizeof(buffer), "[this:%p, primary:%p, next:%p]",
                                decl,
                                decl->primaryDecl,
                                decl->nextDecl);
                            declString.append(buffer);
                        }
#endif

                        getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString);

                        candidateIndex++;
                        if (candidateIndex == maxCandidatesToPrint)
                            break;
                    }
                    if (candidateIndex != candidateCount)
                    {
                        getSink()->diagnose(expr, Diagnostics::moreOverloadCandidates, candidateCount - candidateIndex);
                    }
                }

                return CreateErrorExpr(expr);
            }
            else if (context.bestCandidate)
            {
                // There was one best candidate, even if it might not have been
                // applicable in the end.
                // We will report errors for this one candidate, then, to give
                // the user the most help we can.
                if (shouldAddToCache)
                    typeCheckingCache->resolvedOperatorOverloadCache[key] = *context.bestCandidate;
                return CompleteOverloadCandidate(context, *context.bestCandidate);
            }
            else
            {
                // Nothing at all was found that we could even consider invoking
                getSink()->diagnose(expr->FunctionExpr, Diagnostics::expectedFunction);
                expr->type = QualType(getSession()->getErrorType());
                return expr;
            }
        }

        void AddGenericOverloadCandidate(
            LookupResultItem		baseItem,
            OverloadResolveContext&	context)
        {
            if (auto genericDeclRef = baseItem.declRef.As<GenericDecl>())
            {
                checkDecl(genericDeclRef.getDecl());

                OverloadCandidate candidate;
                candidate.flavor = OverloadCandidate::Flavor::Generic;
                candidate.item = baseItem;
                candidate.resultType = nullptr;

                AddOverloadCandidate(context, candidate);
            }
        }

        void AddGenericOverloadCandidates(
            RefPtr<Expr>	baseExpr,
            OverloadResolveContext&			context)
        {
            if(auto baseDeclRefExpr = baseExpr.As<DeclRefExpr>())
            {
                auto declRef = baseDeclRefExpr->declRef;
                AddGenericOverloadCandidate(LookupResultItem(declRef), context);
            }
            else if (auto overloadedExpr = baseExpr.As<OverloadedExpr>())
            {
                // We are referring to a bunch of declarations, each of which might be generic
                LookupResult result;
                for (auto item : overloadedExpr->lookupResult2.items)
                {
                    AddGenericOverloadCandidate(item, context);
                }
            }
            else
            {
                // any other cases?
            }
        }

        RefPtr<Expr> visitGenericAppExpr(GenericAppExpr * genericAppExpr)
        {
            // We are applying a generic to arguments, but there might be multiple generic
            // declarations with the same name, so this becomes a specialized case of
            // overload resolution.

            // Start by checking the base expression and arguments.
            auto& baseExpr = genericAppExpr->FunctionExpr;
            baseExpr = CheckTerm(baseExpr);
            auto& args = genericAppExpr->Arguments;
            for (auto& arg : args)
            {
                arg = CheckTerm(arg);
            }

            // If there was an error in the base expression,  or in any of
            // the arguments, then just bail.
            if (IsErrorExpr(baseExpr))
            {
                return CreateErrorExpr(genericAppExpr);
            }
            for (auto argExpr : args)
            {
                if (IsErrorExpr(argExpr))
                {
                    return CreateErrorExpr(genericAppExpr);
                }
            }

            // Otherwise, let's start looking at how to find an overload...

            OverloadResolveContext context;
            context.originalExpr = genericAppExpr;
            context.funcLoc = baseExpr->loc;
            context.argCount = args.Count();
            context.args = args.Buffer();
            context.loc = genericAppExpr->loc;

            context.baseExpr = GetBaseExpr(baseExpr);

            AddGenericOverloadCandidates(baseExpr, context);

            if (context.bestCandidates.Count() > 0)
            {
                // Things were ambiguous.
                if (context.bestCandidates[0].status != OverloadCandidate::Status::Appicable)
                {
                    // There were multple equally-good candidates, but none actually usable.
                    // We will construct a diagnostic message to help out.

                    // TODO(tfoley): print a reasonable message here...

                    getSink()->diagnose(genericAppExpr, Diagnostics::unimplemented, "no applicable generic");

                    return CreateErrorExpr(genericAppExpr);
                }
                else
                {
                    // There were multiple viable candidates, but that isn't an error: we just need
                    // to complete all of them and create an overloaded expression as a result.

                    auto overloadedExpr = new OverloadedExpr2();
                    overloadedExpr->base = context.baseExpr;
                    for (auto candidate : context.bestCandidates)
                    {
                        auto candidateExpr = CompleteOverloadCandidate(context, candidate);
                        overloadedExpr->candidiateExprs.Add(candidateExpr);
                    }
                    return overloadedExpr;
                }
            }
            else if (context.bestCandidate)
            {
                // There was one best candidate, even if it might not have been
                // applicable in the end.
                // We will report errors for this one candidate, then, to give
                // the user the most help we can.
                return CompleteOverloadCandidate(context, *context.bestCandidate);
            }
            else
            {
                // Nothing at all was found that we could even consider invoking
                getSink()->diagnose(genericAppExpr, Diagnostics::unimplemented, "expected a generic");
                return CreateErrorExpr(genericAppExpr);
            }
        }

        RefPtr<Expr> visitSharedTypeExpr(SharedTypeExpr* expr)
        {
            if (!expr->type.Ptr())
            {
                expr->base = CheckProperType(expr->base);
                expr->type = expr->base.exp->type;
            }
            return expr;
        }




        RefPtr<Expr> CheckExpr(RefPtr<Expr> expr)
        {
            auto term = CheckTerm(expr);

            // TODO(tfoley): Need a step here to ensure that the term actually
            // resolves to a (single) expression with a real type.

            return term;
        }

        RefPtr<Expr> CheckInvokeExprWithCheckedOperands(InvokeExpr *expr)
        {
            auto rs = ResolveInvoke(expr);
            if (auto invoke = dynamic_cast<InvokeExpr*>(rs.Ptr()))
            {
                // if this is still an invoke expression, test arguments passed to inout/out parameter are LValues
                if(auto funcType = invoke->FunctionExpr->type->As<FuncType>())
                {
                    UInt paramCount = funcType->getParamCount();
                    for (UInt pp = 0; pp < paramCount; ++pp)
                    {
                        auto paramType = funcType->getParamType(pp);
                        if (paramType->As<OutTypeBase>() || paramType->As<RefType>())
                        {
                            // `out`, `inout`, and `ref` parameters currently require
                            // an *exact* match on the type of the argument.
                            //
                            // TODO: relax this requirement by allowing an argument
                            // for an `inout` parameter to be converted in both
                            // directions.
                            //
                            if( pp < expr->Arguments.Count() )
                            {
                                auto argExpr = expr->Arguments[pp];
                                if( !argExpr->type.IsLeftValue )
                                {
                                    getSink()->diagnose(
                                        argExpr,
                                        Diagnostics::argumentExpectedLValue,
                                        pp);

                                    if( auto implicitCastExpr = argExpr.As<ImplicitCastExpr>() )
                                    {
                                        getSink()->diagnose(
                                            argExpr,
                                            Diagnostics::implicitCastUsedAsLValue,
                                            implicitCastExpr->Arguments[0]->type,
                                            implicitCastExpr->type);
                                    }

                                    maybeDiagnoseThisNotLValue(argExpr);
                                }
                            }
                            else
                            {
                                // This implies that the function had an `out`
                                // or `inout` parameter and they gave it a default
                                // argument expression. I'm not even sure what
                                // that would mean.
                                //
                                // TODO: make sure this gets validated on the
                                // declaring side.
                                //
                                SLANG_DIAGNOSE_UNEXPECTED(getSink(), invoke, "default argument expression for out/inout paameter");
                            }
                        }
                    }
                }
            }
            return rs;
        }

        RefPtr<Expr> visitInvokeExpr(InvokeExpr *expr)
        {
            // check the base expression first
            expr->FunctionExpr = CheckExpr(expr->FunctionExpr);
            // Next check the argument expressions
            for (auto & arg : expr->Arguments)
            {
                arg = CheckExpr(arg);
            }

            return CheckInvokeExprWithCheckedOperands(expr);
        }


        RefPtr<Expr> visitVarExpr(VarExpr *expr)
        {
            // If we've already resolved this expression, don't try again.
            if (expr->declRef)
                return expr;

            expr->type = QualType(getSession()->getErrorType());
            auto lookupResult = lookUp(
                getSession(),
                this, expr->name, expr->scope);
            if (lookupResult.isValid())
            {
                return createLookupResultExpr(
                    lookupResult,
                    nullptr,
                    expr->loc);
            }

            getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name);

            return expr;
        }

        RefPtr<Expr> visitTypeCastExpr(TypeCastExpr * expr)
        {
            // Check the term we are applying first
            auto funcExpr = expr->FunctionExpr;
            funcExpr = CheckTerm(funcExpr);

            // Now ensure that the term represnets a (proper) type.
            TypeExp typeExp;
            typeExp.exp = funcExpr;
            typeExp = CheckProperType(typeExp);

            expr->FunctionExpr = typeExp.exp;
            expr->type.type = typeExp.type;

            // Next check the argument expression (there should be only one)
            for (auto & arg : expr->Arguments)
            {
                arg = CheckExpr(arg);
            }

            // Now process this like any other explicit call (so casts
            // and constructor calls are semantically equivalent).
            return CheckInvokeExprWithCheckedOperands(expr);
        }

        // Get the type to use when referencing a declaration
        QualType GetTypeForDeclRef(DeclRef<Decl> declRef)
        {
            return getTypeForDeclRef(
                getSession(),
                this,
                getSink(),
                declRef,
                &typeResult);
        }

        //
        // Some syntax nodes should not occur in the concrete input syntax,
        // and will only appear *after* checking is complete. We need to
        // deal with this cases here, even if they are no-ops.
        //

        RefPtr<Expr> visitDerefExpr(DerefExpr* expr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax");
            return expr;
        }

        RefPtr<Expr> visitSwizzleExpr(SwizzleExpr* expr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax");
            return expr;
        }

        RefPtr<Expr> visitOverloadedExpr(OverloadedExpr* expr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax");
            return expr;
        }

        RefPtr<Expr> visitOverloadedExpr2(OverloadedExpr2* expr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax");
            return expr;
        }


        RefPtr<Expr> visitAggTypeCtorExpr(AggTypeCtorExpr* expr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax");
            return expr;
        }

        //
        //
        //

        RefPtr<Expr> MaybeDereference(RefPtr<Expr> inExpr)
        {
            RefPtr<Expr> expr = inExpr;
            for (;;)
            {
                auto& type = expr->type;
                if (auto pointerLikeType = type->As<PointerLikeType>())
                {
                    type = QualType(pointerLikeType->elementType);

                    auto derefExpr = new DerefExpr();
                    derefExpr->base = expr;
                    derefExpr->type = QualType(pointerLikeType->elementType);

                    // TODO(tfoley): deal with l-value-ness here

                    expr = derefExpr;
                    continue;
                }

                // Default case: just use the expression as-is
                return expr;
            }
        }

        RefPtr<Expr> CheckSwizzleExpr(
            MemberExpr* memberRefExpr,
            RefPtr<Type>      baseElementType,
            IntegerLiteralValue         baseElementCount)
        {
            RefPtr<SwizzleExpr> swizExpr = new SwizzleExpr();
            swizExpr->loc = memberRefExpr->loc;
            swizExpr->base = memberRefExpr->BaseExpression;

            IntegerLiteralValue limitElement = baseElementCount;

            int elementIndices[4];
            int elementCount = 0;

            bool elementUsed[4] = { false, false, false, false };
            bool anyDuplicates = false;
            bool anyError = false;

            auto swizzleText = getText(memberRefExpr->name);

            for (UInt i = 0; i < swizzleText.Length(); i++)
            {
                auto ch = swizzleText[i];
                int elementIndex = -1;
                switch (ch)
                {
                case 'x': case 'r': elementIndex = 0; break;
                case 'y': case 'g': elementIndex = 1; break;
                case 'z': case 'b': elementIndex = 2; break;
                case 'w': case 'a': elementIndex = 3; break;
                default:
                    // An invalid character in the swizzle is an error
                    getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString());
                    anyError = true;
                    continue;
                }

                // TODO(tfoley): GLSL requires that all component names
                // come from the same "family"...

                // Make sure the index is in range for the source type
                if (elementIndex >= limitElement)
                {
                    getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString());
                    anyError = true;
                    continue;
                }

                // Check if we've seen this index before
                for (int ee = 0; ee < elementCount; ee++)
                {
                    if (elementIndices[ee] == elementIndex)
                        anyDuplicates = true;
                }

                // add to our list...
                elementIndices[elementCount++] = elementIndex;
            }

            for (int ee = 0; ee < elementCount; ++ee)
            {
                swizExpr->elementIndices[ee] = elementIndices[ee];
            }
            swizExpr->elementCount = elementCount;

            if (anyError)
            {
                return CreateErrorExpr(memberRefExpr);
            }
            else if (elementCount == 1)
            {
                // single-component swizzle produces a scalar
                //
                // Note(tfoley): the official HLSL rules seem to be that it produces
                // a one-component vector, which is then implicitly convertible to
                // a scalar, but that seems like it just adds complexity.
                swizExpr->type = QualType(baseElementType);
            }
            else
            {
                // TODO(tfoley): would be nice to "re-sugar" type
                // here if the input type had a sugared name...
                swizExpr->type = QualType(createVectorType(
                    baseElementType,
                    new ConstantIntVal(elementCount)));
            }

            // A swizzle can be used as an l-value as long as there
            // were no duplicates in the list of components
            swizExpr->type.IsLeftValue = !anyDuplicates;

            return swizExpr;
        }

        RefPtr<Expr> CheckSwizzleExpr(
            MemberExpr*	memberRefExpr,
            RefPtr<Type>		baseElementType,
            RefPtr<IntVal>				baseElementCount)
        {
            if (auto constantElementCount = baseElementCount.As<ConstantIntVal>())
            {
                return CheckSwizzleExpr(memberRefExpr, baseElementType, constantElementCount->value);
            }
            else
            {
                getSink()->diagnose(memberRefExpr, Diagnostics::unimplemented, "swizzle on vector of unknown size");
                return CreateErrorExpr(memberRefExpr);
            }
        }

        RefPtr<Expr> visitStaticMemberExpr(StaticMemberExpr* /*expr*/)
        {
            SLANG_UNEXPECTED("should not occur in unchecked AST");
            UNREACHABLE_RETURN(nullptr);
        }

        RefPtr<Expr> lookupResultFailure(
            MemberExpr*     expr,
            QualType const& baseType)
        {
            getSink()->diagnose(expr, Diagnostics::noMemberOfNameInType, expr->name, baseType);
            expr->type = QualType(getSession()->getErrorType());
            return expr;

        }

        RefPtr<Expr> visitMemberExpr(MemberExpr * expr)
        {
            expr->BaseExpression = CheckExpr(expr->BaseExpression);

            expr->BaseExpression = MaybeDereference(expr->BaseExpression);

            auto & baseType = expr->BaseExpression->type;

            // Note: Checking for vector types before declaration-reference types,
            // because vectors are also declaration reference types...
            //
            // Also note: the way this is done right now means that the ability
            // to swizzle vectors interferes with any chance of looking up
            // members via extension, for vector or scalar types.
            //
            // TODO: Matrix swizzles probably need to be handled at some point.
            if (auto baseVecType = baseType->AsVectorType())
            {
                return CheckSwizzleExpr(
                    expr,
                    baseVecType->elementType,
                    baseVecType->elementCount);
            }
            else if(auto baseScalarType = baseType->AsBasicType())
            {
                // Treat scalar like a 1-element vector when swizzling
                return CheckSwizzleExpr(
                    expr,
                    baseScalarType,
                    1);
            }
            else if(auto typeType = baseType->As<TypeType>())
            {
                // We are looking up a member inside a type.
                // We want to be careful here because we should only find members
                // that are implicitly or explicitly `static`.
                //
                // TODO: this duplicates a *lot* of logic with the case below.
                // We need to fix that.
                auto type = typeType->type;

                if (type->As<ErrorType>())
                {
                    return CreateErrorExpr(expr);
                }

                LookupResult lookupResult = lookUpMember(
                    getSession(),
                    this,
                    expr->name,
                    type);
                if (!lookupResult.isValid())
                {
                    return lookupResultFailure(expr, baseType);
                }

                // We need to confirm that whatever member we
                // are trying to refer to is usable via static reference.
                //
                // TODO: eventually we might allow a non-static
                // member to be adapted by turning it into something
                // like a closure that takes the missing `this` parameter.
                //
                // E.g., a static reference to a method could be treated
                // as a value with a function type, where the first parameter
                // is `type`.
                //
                // The biggest challenge there is that we'd need to arrange
                // to generate "dispatcher" functions that could be used
                // to implement that function, in the case where we are
                // making a static reference to some kind of polymoprhic declaration.
                //
                // (Also, static refernces to fields/properties would get even
                // harder, because you'd have to know whether a getter/setter/ref-er
                // is needed).
                //
                // For now let's just be expedient and disallow all of that, because
                // we can always add it back in later.

                if(!lookupResult.isOverloaded())
                {
                    // The non-overloaded case is relatively easy. We just want
                    // to look at the member being referenced, and check if
                    // it is allowed in a `static` context:
                    //
                    if(!isUsableAsStaticMember(lookupResult.item))
                    {
                        getSink()->diagnose(
                            expr->loc,
                            Diagnostics::staticRefToNonStaticMember,
                            type,
                            expr->name);
                    }
                }
                else
                {
                    // The overloaded case is trickier, because we should first
                    // filter the list of candidates, because if there is anything
                    // that *is* usable in a static context, then we should assume
                    // the user just wants to reference that. We should only
                    // issue an error if *all* of the items that were discovered
                    // are non-static.
                    bool anyNonStatic = false;
                    List<LookupResultItem> staticItems;
                    for(auto item : lookupResult.items)
                    {
                        // Is this item usable as a static member?
                        if(isUsableAsStaticMember(item))
                        {
                            // If yes, then it will be part of the output.
                            staticItems.Add(item);
                        }
                        else
                        {
                            // If no, then we might need to output an error.
                            anyNonStatic = true;
                        }
                    }

                    // Was there anything non-static in the list?
                    if(anyNonStatic)
                    {
                        // If we had some static items, then that's okay,
                        // we just want to use our newly-filtered list.
                        if(staticItems.Count())
                        {
                            lookupResult.items = staticItems;
                        }
                        else
                        {
                            // Otherwise, it is time to report an error.
                            getSink()->diagnose(
                                expr->loc,
                                Diagnostics::staticRefToNonStaticMember,
                                type,
                                expr->name);
                        }
                    }
                    // If there were no non-static items, then the `items`
                    // array already represents what we'd get by filtering...
                }

                return createLookupResultExpr(
                    lookupResult,
                    expr->BaseExpression,
                    expr->loc);
            }
            else if (baseType->As<ErrorType>())
            {
                return CreateErrorExpr(expr);
            }
            else
            {
                LookupResult lookupResult = lookUpMember(
                    getSession(),
                    this,
                    expr->name,
                    baseType.Ptr());
                if (!lookupResult.isValid())
                {
                    return lookupResultFailure(expr, baseType);
                }

                // TODO: need to filter for declarations that are valid to refer
                // to in this context...

                return createLookupResultExpr(
                    lookupResult,
                    expr->BaseExpression,
                    expr->loc);
            }
        }
        SemanticsVisitor & operator = (const SemanticsVisitor &) = delete;


        //

        RefPtr<Expr> visitInitializerListExpr(InitializerListExpr* expr)
        {
            // When faced with an initializer list, we first just check the sub-expressions blindly.
            // Actually making them conform to a desired type will wait for when we know the desired
            // type based on context.

            for( auto& arg : expr->args )
            {
                arg = CheckTerm(arg);
            }

            expr->type = getSession()->getInitializerListType();

            return expr;
        }

        void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl)
        {
            // If we've imported this one already, then
            // skip the step where we modify the current scope.
            if (importedModules.Contains(moduleDecl))
            {
                return;
            }
            importedModules.Add(moduleDecl);


            // Create a new sub-scope to wire the module
            // into our lookup chain.
            auto subScope = new Scope();
            subScope->containerDecl = moduleDecl;

            subScope->nextSibling = scope->nextSibling;
            scope->nextSibling = subScope;

            // Also import any modules from nested `import` declarations
            // with the `__exported` modifier
            for (auto importDecl : moduleDecl->getMembersOfType<ImportDecl>())
            {
                if (!importDecl->HasModifier<ExportedModifier>())
                    continue;

                importModuleIntoScope(scope, importDecl->importedModuleDecl.Ptr());
            }
        }

        void visitEmptyDecl(EmptyDecl* /*decl*/)
        {
            // nothing to do
        }

        void visitImportDecl(ImportDecl* decl)
        {
            if(decl->IsChecked(DeclCheckState::CheckedHeader))
                return;

            // We need to look for a module with the specified name
            // (whether it has already been loaded, or needs to
            // be loaded), and then put its declarations into
            // the current scope.

            auto name = decl->moduleNameAndLoc.name;
            auto scope = decl->scope;

            // Try to load a module matching the name
            auto importedModuleDecl = findOrImportModule(request, name, decl->moduleNameAndLoc.loc);

            // If we didn't find a matching module, then bail out
            if (!importedModuleDecl)
                return;

            // Record the module that was imported, so that we can use
            // it later during code generation.
            decl->importedModuleDecl = importedModuleDecl;

            importModuleIntoScope(scope.Ptr(), importedModuleDecl.Ptr());

            decl->SetCheckState(getCheckedState());
        }

        // Perform semantic checking of an object-oriented `this`
        // expression.
        RefPtr<Expr> visitThisExpr(ThisExpr* expr)
        {
            // A `this` expression will default to immutable.
            expr->type.IsLeftValue = false;

            // We will do an upwards search starting in the current
            // scope, looking for a surrounding type (or `extension`)
            // declaration that could be the referrant of the expression.
            auto scope = expr->scope;
            while (scope)
            {
                auto containerDecl = scope->containerDecl;

                if( auto funcDeclBase = containerDecl->As<FunctionDeclBase>() )
                {
                    if( funcDeclBase->HasModifier<MutatingAttribute>() )
                    {
                        expr->type.IsLeftValue = true;
                    }
                }
                else if (auto aggTypeDecl = containerDecl->As<AggTypeDecl>())
                {
                    checkDecl(aggTypeDecl);

                    // Okay, we are using `this` in the context of an
                    // aggregate type, so the expression should be
                    // of the corresponding type.
                    expr->type.type = DeclRefType::Create(
                        getSession(),
                        makeDeclRef(aggTypeDecl));
                    return expr;
                }
                else if (auto extensionDecl = containerDecl->As<ExtensionDecl>())
                {
                    checkDecl(extensionDecl);

                    // When `this` is used in the context of an `extension`
                    // declaration, then it should refer to an instance of
                    // the type being extended.
                    //
                    // TODO: There is potentially a small gotcha here that
                    // lookup through such a `this` expression should probably
                    // prioritize members declared in the current extension
                    // if there are multiple extensions in scope that add
                    // members with the same name...
                    //
                    expr->type.type = extensionDecl->targetType.type;
                    return expr;
                }

                scope = scope->parent;
            }

            getSink()->diagnose(expr, Diagnostics::thisExpressionOutsideOfTypeDecl);
            return CreateErrorExpr(expr);
        }
    };

    bool isPrimaryDecl(
        CallableDecl*   decl)
    {
        SLANG_ASSERT(decl);
        return (!decl->primaryDecl) || (decl == decl->primaryDecl);
    }

    RefPtr<Type> checkProperType(TranslationUnitRequest * tu, TypeExp typeExp)
    {
        RefPtr<Type> type;
        DiagnosticSink nSink;
        nSink.sourceManager = tu->compileRequest->sourceManager;
        SemanticsVisitor visitor(
            &nSink,
            tu->compileRequest,
            tu);
        auto typeOut = visitor.CheckProperType(typeExp);
        if (!nSink.errorCount)
        {
            type = typeOut.type;
        }
        return type;
    }


    FuncDecl* findFunctionDeclByName(EntryPointRequest* entryPoint, Name* name)
    {
        auto translationUnit = entryPoint->getTranslationUnit();
        auto sink = &entryPoint->compileRequest->mSink;
        auto translationUnitSyntax = translationUnit->SyntaxNode;

        // Make sure we've got a query-able member dictionary
        buildMemberDictionary(translationUnitSyntax);

        // We will look up any global-scope declarations in the translation
        // unit that match the name of our entry point.
        Decl* firstDeclWithName = nullptr;
        if (!translationUnitSyntax->memberDictionary.TryGetValue(name, firstDeclWithName))
        {
            // If there doesn't appear to be any such declaration, then we are done.

            sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, name);

            return nullptr;
        }

        // We found at least one global-scope declaration with the right name,
        // but (1) it might not be a function, and (2) there might be
        // more than one function.
        //
        // We'll walk the linked list of declarations with the same name,
        // to see what we find. Along the way we'll keep track of the
        // first function declaration we find, if any:
        FuncDecl* entryPointFuncDecl = nullptr;
        for (auto ee = firstDeclWithName; ee; ee = ee->nextInContainerWithSameName)
        {
            // Is this declaration a function?
            if (auto funcDecl = dynamic_cast<FuncDecl*>(ee))
            {
                // Skip non-primary declarations, so that
                // we don't give an error when an entry
                // point is forward-declared.
                if (!isPrimaryDecl(funcDecl))
                    continue;

                // is this the first one we've seen?
                if (!entryPointFuncDecl)
                {
                    // If so, this is a candidate to be
                    // the entry point function.
                    entryPointFuncDecl = funcDecl;
                }
                else
                {
                    // Uh-oh! We've already seen a function declaration with this
                    // name before, so the whole thing is ambiguous. We need
                    // to diagnose and bail out.

                    sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, name);

                    // List all of the declarations that the user *might* mean
                    for (auto ff = firstDeclWithName; ff; ff = ff->nextInContainerWithSameName)
                    {
                        if (auto candidate = dynamic_cast<FuncDecl*>(ff))
                        {
                            sink->diagnose(candidate, Diagnostics::entryPointCandidate, candidate->getName());
                        }
                    }

                    // Bail out.
                    return nullptr;
                }
            }
        }

        return entryPointFuncDecl;
    }

    // Validate that an entry point function conforms to any additional
    // constraints based on the stage (and profile?) it specifies.
    void validateEntryPoint(
        EntryPointRequest* entryPoint)
    {
        // TODO: We currently do minimal checking here, but this is the
        // right place to perform the following validation checks:
        //

        // * Are the function input/output parameters and result type
        //   all valid for the chosen stage? (e.g., there shouldn't be
        //   an `OutputStream<X>` type in a vertex shader signature)
        //
        // * For any varying input/output, are there semantics specified
        //   (Note: this potentially overlaps with layout logic...), and
        //   are the system-value semantics valid for the given stage?
        //
        //   There's actually a lot of detail to semantic checking, in
        //   that the AST-level code should probably be validating the
        //   use of system-value semantics by linking them to explicit
        //   declarations in the standard library. We should also be
        //   using profile information on those declarations to infer
        //   appropriate profile restrictions on the entry point.
        //
        // * Is the entry point actually usable on the given stage/profile?
        //   E.g., if we have a vertex shader that (transitively) calls
        //   `Texture2D.Sample`, then that should produce an error because
        //   that function is specific to the fragment profile/stage.
        //

        auto sink = &entryPoint->compileRequest->mSink;

        // Every entry point needs to have a stage specified either via
        // command-line/API options, or via an explicit `[shader("...")]` attribute.
        //
        if( entryPoint->getStage() == Stage::Unknown )
        {
            sink->diagnose(entryPoint->decl, Diagnostics::entryPointHasNoStage, entryPoint->name);
        }

        if (entryPoint->getStage() == Stage::Hull)
        {
            auto translationUnit = entryPoint->getTranslationUnit();
            auto translationUnitSyntax = translationUnit->SyntaxNode;

            auto attr = entryPoint->decl->FindModifier<PatchConstantFuncAttribute>();

            if (attr)
            {
                if (attr->args.Count() != 1)
                {
                    sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
                    return;
                }

                Expr* expr = attr->args[0];
                StringLiteralExpr* stringLit = expr->As<StringLiteralExpr>();

                if (!stringLit)
                {
                    sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
                    return;
                }

                Name* name = entryPoint->compileRequest->getNamePool()->getName(stringLit->value);
                FuncDecl* funcDecl = findFunctionDeclByName(entryPoint, name);
                if (!funcDecl)
                {
                    sink->diagnose(translationUnitSyntax, Diagnostics::attributeFunctionNotFound, name, "patchconstantfunc");
                    return;
                }

                attr->patchConstantFuncDecl = funcDecl;
            }
        }
    }

    // Given an `EntryPointRequest` specified via API or command line options,
    // attempt to find a matching AST declaration that implements the specified
    // entry point. If such a function is found, then validate that it actually
    // meets the requirements for the selected stage/profile.
    //
    void findAndValidateEntryPoint(
        EntryPointRequest*  entryPoint)
    {
        // The first step in validating the entry point is to find
        // the (unique) function declaration that matches its name.

        auto translationUnit = entryPoint->getTranslationUnit();
        auto sink = &entryPoint->compileRequest->mSink;
        auto translationUnitSyntax = translationUnit->SyntaxNode;


        // Make sure we've got a query-able member dictionary
        buildMemberDictionary(translationUnitSyntax);

        // We will look up any global-scope declarations in the translation
        // unit that match the name of our entry point.
        Decl* firstDeclWithName = nullptr;
        if( !translationUnitSyntax->memberDictionary.TryGetValue(entryPoint->name, firstDeclWithName) )
        {
            // If there doesn't appear to be any such declaration, then
            // we need to diagnose it as an error, and then bail out.
            sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, entryPoint->name);
            return;
        }

        // We found at least one global-scope declaration with the right name,
        // but (1) it might not be a function, and (2) there might be
        // more than one function.
        //
        // We'll walk the linked list of declarations with the same name,
        // to see what we find. Along the way we'll keep track of the
        // first function declaration we find, if any:
        FuncDecl* entryPointFuncDecl = nullptr;
        for(auto ee = firstDeclWithName; ee; ee = ee->nextInContainerWithSameName)
        {
            // Is this declaration a function?
            if (auto funcDecl = dynamic_cast<FuncDecl*>(ee))
            {
                // Skip non-primary declarations, so that
                // we don't give an error when an entry
                // point is forward-declared.
                if (!isPrimaryDecl(funcDecl))
                    continue;

                // is this the first one we've seen?
                if (!entryPointFuncDecl)
                {
                    // If so, this is a candidate to be
                    // the entry point function.
                    entryPointFuncDecl = funcDecl;
                }
                else
                {
                    // Uh-oh! We've already seen a function declaration with this
                    // name before, so the whole thing is ambiguous. We need
                    // to diagnose and bail out.

                    sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, entryPoint->name);

                    // List all of the declarations that the user *might* mean
                    for (auto ff = firstDeclWithName; ff; ff = ff->nextInContainerWithSameName)
                    {
                        if (auto candidate = dynamic_cast<FuncDecl*>(ff))
                        {
                            sink->diagnose(candidate, Diagnostics::entryPointCandidate, candidate->getName());
                        }
                    }

                    // Bail out.
                    return;
                }
            }
        }

        // Did we find a function declaration in our search?
        if(!entryPointFuncDecl)
        {
            // If not, then we need to diagnose the error.
            // For convenience, we will point to the first
            // declaration with the right name, that wasn't a function.
            sink->diagnose(firstDeclWithName, Diagnostics::entryPointSymbolNotAFunction, entryPoint->name);
            return;
        }

        // If the entry point specifies a stage via a `[shader("...")]` attribute,
        // then we might be able to infer a stage for the entry point request if
        // it didn't have one, *or* issue a diagnostic if there is a mismatch.
        //
        if( auto entryPointAttribute = entryPointFuncDecl->FindModifier<EntryPointAttribute>() )
        {
            if( entryPoint->getStage() == Stage::Unknown )
            {
                entryPoint->profile.setStage(entryPointAttribute->stage);
            }
            else if( entryPointAttribute->stage != entryPoint->getStage() )
            {
                sink->diagnose(entryPointFuncDecl, Diagnostics::specifiedStageDoesntMatchAttribute, entryPoint->name, entryPoint->getStage(), entryPointAttribute->stage);
            }
        }

        // TODO: it is possible that the entry point was declared with
        // profile or target overloading. Is there anything that we need
        // to do at this point to filter out declarations that aren't
        // relevant to the selected profile for the entry point?

        // Phew, we have at least found a suitable decl.
        // Let's record that in the entry-point request so
        // that we don't have to re-do this effort again later.
        entryPoint->decl = entryPointFuncDecl;

        // Lookup generic parameter types in global scope
        List<RefPtr<Scope>> scopesToTry;
        scopesToTry.Add(entryPoint->getTranslationUnit()->SyntaxNode->scope);
        for (auto & module : entryPoint->compileRequest->loadedModulesList)
            scopesToTry.Add(module->moduleDecl->scope);

        List<RefPtr<Type>> globalGenericArgs;
        for (auto name : entryPoint->genericParameterTypeNames)
        {
            // parse type name
            RefPtr<Type> type;
            for (auto & s : scopesToTry)
            {
                RefPtr<Expr> typeExpr = entryPoint->compileRequest->parseTypeString(entryPoint->getTranslationUnit(),
                    name, s);
                type = checkProperType(translationUnit, TypeExp(typeExpr));
                if (type)
                {
                    break;
                }
            }
            if (!type)
            {
                sink->diagnose(firstDeclWithName, Diagnostics::entryPointTypeSymbolNotAType, name);
                return;
            }

            globalGenericArgs.Add(type);
        }

        // validate global type arguments only when we are generating code
        if ((entryPoint->compileRequest->compileFlags & SLANG_COMPILE_FLAG_NO_CODEGEN) == 0)
        {
            // check that user-provioded type arguments conforms to the generic type
            // parameter declaration of this translation unit

            // collect global generic parameters from all imported modules
            List<RefPtr<GlobalGenericParamDecl>> globalGenericParams;
            // add current translation unit first
            {
                auto globalGenParams = translationUnit->SyntaxNode->getMembersOfType<GlobalGenericParamDecl>();
                for (auto p : globalGenParams)
                    globalGenericParams.Add(p);
            }
            // add imported modules
            for (auto loadedModule : entryPoint->compileRequest->loadedModulesList)
            {
                auto moduleDecl = loadedModule->moduleDecl;
                auto globalGenParams = moduleDecl->getMembersOfType<GlobalGenericParamDecl>();
                for (auto p : globalGenParams)
                    globalGenericParams.Add(p);
            }

            if (globalGenericParams.Count() != globalGenericArgs.Count())
            {
                sink->diagnose(entryPoint->decl, Diagnostics::mismatchEntryPointTypeArgument,
                    globalGenericParams.Count(),
                    globalGenericArgs.Count());
                return;
            }

            // We have an appropriate number of arguments for the global generic parameters,
            // and now we need to check that the arguments conform to the declared constraints.
            //
            // Along the way, we will build up an appropriate set of substitutions to represent
            // the generic arguments and their conformances.
            //
            RefPtr<Substitutions> globalGenericSubsts;
            auto globalGenericSubstLink = &globalGenericSubsts;
            //
            // TODO: There is a serious flaw to this checking logic if we ever have cases where
            // the constraints on one `type_param` can depend on another `type_param`, e.g.:
            //
            //      type_param A;
            //      type_param B : ISidekick<A>;
            //
            // In that case, if a user tries to set `B` to `Robin` and `Robin` conforms to
            // `ISidekick<Batman>`, then the compiler needs to know whether `A` is being
            // set to `Batman` to know whether the setting for `B` is valid. In this limit
            // the constraints can be mutually recursive (so `A : IMentor<B>`).
            //
            // The only way to check things correctly is to validate each conformance under
            // a set of assumptions (substitutions) that includes all the type substitutions,
            // and possibly also all the other constraints *except* the one to be validated.
            //
            // We will punt on this for now, and just check each constraint in isolation.
            //
            UInt argCounter = 0;
            for(auto& globalGenericParam : globalGenericParams)
            {
                // Get the argument that matches this parameter.
                UInt argIndex = argCounter++;
                SLANG_ASSERT(argIndex < globalGenericArgs.Count());
                auto globalGenericArg = globalGenericArgs[argIndex];

                // As a quick sanity check, see if the argument that is being supplied for a parameter
                // is just the parameter itself, because this should always be an error:
                //
                if( auto argDeclRefType = globalGenericArg->As<DeclRefType>() )
                {
                    auto argDeclRef = argDeclRefType->declRef;
                    if(auto argGenericParamDeclRef = argDeclRef.As<GlobalGenericParamDecl>())
                    {
                        if(argGenericParamDeclRef.getDecl() == globalGenericParam)
                        {
                            // We are trying to specialize a generic parameter using itself.
                            sink->diagnose(globalGenericParam,
                                Diagnostics::cannotSpecializeGlobalGenericToItself,
                                globalGenericParam->getName());
                            sink->diagnose(entryPointFuncDecl,
                                Diagnostics::noteWhenCompilingEntryPoint,
                                entryPointFuncDecl->getName());
                            continue;
                        }
                        else
                        {
                            // We are trying to specialize a generic parameter using a *different*
                            // global generic type parameter.
                            sink->diagnose(globalGenericParam,
                                Diagnostics::cannotSpecializeGlobalGenericToAnotherGenericParam,
                                globalGenericParam->getName(),
                                argGenericParamDeclRef.GetName());
                            sink->diagnose(entryPointFuncDecl,
                                Diagnostics::noteWhenCompilingEntryPoint,
                                entryPointFuncDecl->getName());
                            continue;
                        }
                    }
                }


                // Create a substitution for this parameter/argument.
                RefPtr<GlobalGenericParamSubstitution> subst = new GlobalGenericParamSubstitution();
                subst->paramDecl = globalGenericParam;
                subst->actualType = globalGenericArg;

                // Walk through the declared constraints for the parameter,
                // and check that the argument actually satisfies them.
                for(auto constraint : globalGenericParam->getMembersOfType<GenericTypeConstraintDecl>())
                {
                    // Get the type that the constraint is enforcing conformance to
                    auto interfaceType = GetSup(DeclRef<GenericTypeConstraintDecl>(constraint, nullptr));

                    // Use our semantic-checking logic to search for a witness to the required conformance
                    SemanticsVisitor visitor(sink, entryPoint->compileRequest, translationUnit);
                    auto witness = visitor.tryGetSubtypeWitness(globalGenericArg, interfaceType);
                    if (!witness)
                    {
                        // If no witness was found, then we will be unable to satisfy
                        // the conformances required.
                        sink->diagnose(globalGenericParam,
                            Diagnostics::typeArgumentDoesNotConformToInterface,
                            globalGenericParam->nameAndLoc.name,
                            globalGenericArg,
                            interfaceType);
                    }

                    // Attach the concrete witness for this conformance to the
                    // substutiton
                    GlobalGenericParamSubstitution::ConstraintArg constraintArg;
                    constraintArg.decl = constraint;
                    constraintArg.val = witness;
                    subst->constraintArgs.Add(constraintArg);
                }

                // Add the substitution for this parameter to the global substitution
                // set that we are building.

                *globalGenericSubstLink = subst;
                globalGenericSubstLink = &subst->outer;
            }

            entryPoint->globalGenericSubst = globalGenericSubsts;
        }
        if (sink->errorCount != 0)
            return;

        // Now that we've *found* the entry point, it is time to validate
        // that it actually meets the constraints for the chosen stage/profile.
        //
        // TODO: This validation should be performed "under" any global generic
        // parameter substitution we might have created, so that we can validate
        // based on knowledge of actual types.
        //
        validateEntryPoint(entryPoint);
    }

    void validateEntryPoints(
        CompileRequest* compileRequest)
    {
        // The validation of entry points here will be modal, and controlled
        // by whether the user specified any entry points directly via
        // API or command-line options.
        //
        // TODO: We may want to make this choice explicit rather than implicit.
        //
        // First, check if the user request any entry points explicitly via
        // the API or command line.
        //
        bool anyExplicitEntryPointRequests = false;
        for (auto& translationUnit : compileRequest->translationUnits)
        {
            if( translationUnit->entryPoints.Count() != 0)
            {
                anyExplicitEntryPointRequests = true;
                break;
            }
        }

        if( anyExplicitEntryPointRequests )
        {
            // If there were any explicit requests for entry points to be
            // checked, then we will *only* check those.

            for (auto& translationUnit : compileRequest->translationUnits)
            {
                for (auto entryPoint : translationUnit->entryPoints)
                {
                    findAndValidateEntryPoint(entryPoint);
                }
            }
        }
        else
        {
            // Otherwise, scan for any `[shader(...)]` attributes in
            // the user's code, and construct `EntryPointRequest`s to
            // represent them.
            //
            // This ensures that downstream code only has to consider
            // the central list of entry point requests, and doesn't
            // have to know where they came from.

            // TODO: A comprehensive approach here would need to search
            // recursively for entry points, because they might appear
            // as, e.g., member function of a `struct` type.
            //
            // For now we'll start with an extremely basic approach that
            // should work for typical HLSL code.
            //
            UInt translationUnitCount = compileRequest->translationUnits.Count();
            for(UInt tt = 0; tt < translationUnitCount; ++tt)
            {
                auto translationUnit = compileRequest->translationUnits[tt];
                for( auto globalDecl : translationUnit->SyntaxNode->Members )
                {
                    auto maybeFuncDecl = globalDecl;
                    if( auto genericDecl = maybeFuncDecl->As<GenericDecl>() )
                    {
                        maybeFuncDecl = genericDecl->inner;
                    }

                    auto funcDecl = maybeFuncDecl->As<FuncDecl>();
                    if(!funcDecl)
                        continue;

                    auto entryPointAttr = funcDecl->FindModifier<EntryPointAttribute>();
                    if(!entryPointAttr)
                        continue;

                    // We've discovered a valid entry point. It is a function (possibly
                    // generic) that has a `[shader(...)]` attribute to mark it as an
                    // entry point.
                    //
                    // We will now register that entry point as an `EntryPointRequest`
                    // with an appropriately chosen profile.
                    //
                    // The profile will only include a stage, so that the profile "family"
                    // and "version" are left unspecified. Downstream code will need
                    // to be able to handle this case.
                    //
                    Profile profile;
                    profile.setStage(entryPointAttr->stage);

                    // We manually fill in the entry point request object.
                    RefPtr<EntryPointRequest> entryPointReq = new EntryPointRequest();
                    entryPointReq->compileRequest = compileRequest;
                    entryPointReq->translationUnitIndex = int(tt);
                    entryPointReq->decl = funcDecl;
                    entryPointReq->name = funcDecl->getName();
                    entryPointReq->profile = profile;

                    // Apply the common validation logic to this entry point.
                    validateEntryPoint(entryPointReq);

                    // Add the entry point to the list in the translation unit,
                    // and also the global list in the compile request.
                    translationUnit->entryPoints.Add(entryPointReq);
                    compileRequest->entryPoints.Add(entryPointReq);
                }
            }
        }
    }

    void checkTranslationUnit(
        TranslationUnitRequest* translationUnit)
    {
        SemanticsVisitor visitor(
            &translationUnit->compileRequest->mSink,
            translationUnit->compileRequest,
            translationUnit);

        // Apply the visitor to do the main semantic
        // checking that is required on all declarations
        // in the translation unit.
        visitor.checkDecl(translationUnit->SyntaxNode);
    }


    //

    // Get the type to use when referencing a declaration
    QualType getTypeForDeclRef(
        Session*                session,
        SemanticsVisitor*       sema,
        DiagnosticSink*         sink,
        DeclRef<Decl>           declRef,
        RefPtr<Type>* outTypeResult)
    {
        if( sema )
        {
            sema->checkDecl(declRef.getDecl());
        }

        // We need to insert an appropriate type for the expression, based on
        // what we found.
        if (auto varDeclRef = declRef.As<VarDeclBase>())
        {
            QualType qualType;
            qualType.type = GetType(varDeclRef);

            bool isLValue = true;
            if(varDeclRef.getDecl()->FindModifier<ConstModifier>())
                isLValue = false;

            qualType.IsLeftValue = isLValue;
            return qualType;
        }
        else if (auto typeAliasDeclRef = declRef.As<TypeDefDecl>())
        {
            auto type = getNamedType(session, typeAliasDeclRef);
            *outTypeResult = type;
            return QualType(getTypeType(type));
        }
        else if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>())
        {
            auto type = DeclRefType::Create(session, aggTypeDeclRef);
            *outTypeResult = type;
            return QualType(getTypeType(type));
        }
        else if (auto simpleTypeDeclRef = declRef.As<SimpleTypeDecl>())
        {
            auto type = DeclRefType::Create(session, simpleTypeDeclRef);
            *outTypeResult = type;
            return QualType(getTypeType(type));
        }
        else if (auto genericDeclRef = declRef.As<GenericDecl>())
        {
            auto type = getGenericDeclRefType(session, genericDeclRef);
            *outTypeResult = type;
            return QualType(getTypeType(type));
        }
        else if (auto funcDeclRef = declRef.As<CallableDecl>())
        {
            auto type = getFuncType(session, funcDeclRef);
            return QualType(type);
        }
        else if (auto constraintDeclRef = declRef.As<TypeConstraintDecl>())
        {
            // When we access a constraint or an inheritance decl (as a member),
            // we are conceptually performing a "cast" to the given super-type,
            // with the declaration showing that such a cast is legal.
            auto type = GetSup(constraintDeclRef);
            return QualType(type);
        }
        if( sink )
        {
            sink->diagnose(declRef, Diagnostics::unimplemented, "cannot form reference to this kind of declaration");
        }
        return QualType(session->getErrorType());
    }

    QualType getTypeForDeclRef(
        Session*        session,
        DeclRef<Decl>   declRef)
    {
        RefPtr<Type> typeResult;
        return getTypeForDeclRef(session, nullptr, nullptr, declRef, &typeResult);
    }

    DeclRef<ExtensionDecl> ApplyExtensionToType(
        SemanticsVisitor*       semantics,
        ExtensionDecl*          extDecl,
        RefPtr<Type>  type)
    {
        if(!semantics)
            return DeclRef<ExtensionDecl>();

        return semantics->ApplyExtensionToType(extDecl, type);
    }

    RefPtr<GenericSubstitution> createDefaultSubsitutionsForGeneric(
        Session*                session,
        GenericDecl*            genericDecl,
        RefPtr<Substitutions>   outerSubst)
    {
        RefPtr<GenericSubstitution> genericSubst = new GenericSubstitution();
        genericSubst->genericDecl = genericDecl;
        genericSubst->outer = outerSubst;

        for( auto mm : genericDecl->Members )
        {
            if( auto genericTypeParamDecl = mm.As<GenericTypeParamDecl>() )
            {
                genericSubst->args.Add(DeclRefType::Create(session, DeclRef<Decl>(genericTypeParamDecl.Ptr(), outerSubst)));
            }
            else if( auto genericValueParamDecl = mm.As<GenericValueParamDecl>() )
            {
                genericSubst->args.Add(new GenericParamIntVal(DeclRef<GenericValueParamDecl>(genericValueParamDecl.Ptr(), outerSubst)));
            }
        }

        // create default substitution arguments for constraints
        for (auto mm : genericDecl->Members)
        {
            if (auto genericTypeConstraintDecl = mm.As<GenericTypeConstraintDecl>())
            {
                RefPtr<DeclaredSubtypeWitness> witness = new DeclaredSubtypeWitness();
                witness->declRef = DeclRef<Decl>(genericTypeConstraintDecl.Ptr(), outerSubst);
                witness->sub = genericTypeConstraintDecl->sub.type;
                witness->sup = genericTypeConstraintDecl->sup.type;
                genericSubst->args.Add(witness);
            }
        }

        return genericSubst;
    }

    // Sometimes we need to refer to a declaration the way that it would be specialized
    // inside the context where it is declared (e.g., with generic parameters filled in
    // using their archetypes).
    //
    SubstitutionSet createDefaultSubstitutions(
        Session*        session,
        Decl*           decl,
        SubstitutionSet outerSubstSet)
    {
        auto dd = decl->ParentDecl;
        if( auto genericDecl = dynamic_cast<GenericDecl*>(dd) )
        {
            // We don't want to specialize references to anything
            // other than the "inner" declaration itself.
            if(decl != genericDecl->inner)
                return outerSubstSet;

            RefPtr<GenericSubstitution> genericSubst = createDefaultSubsitutionsForGeneric(
                session,
                genericDecl,
                outerSubstSet.substitutions);

            return SubstitutionSet(genericSubst);
        }

        return outerSubstSet;
    }

    SubstitutionSet createDefaultSubstitutions(
        Session* session,
        Decl*   decl)
    {
        SubstitutionSet subst;
        if( auto parentDecl = decl->ParentDecl )
        {
            subst = createDefaultSubstitutions(session, parentDecl);
        }
        subst = createDefaultSubstitutions(session, decl, subst);
        return subst;
    }

    void checkDecl(SemanticsVisitor* visitor, Decl* decl)
    {
        visitor->checkDecl(decl);
    }
}
back to top