https://github.com/shader-slang/slang
Raw File
Tip revision: e59516fa8c3a16eb7b99a928c5b85b97bf44fd72 authored by Yong He on 01 February 2022, 00:26:03 UTC
Revise entrypoint renaming interface. (#2113)
Tip revision: e59516f
slang-check-decl.cpp
// slang-check-decl.cpp
#include "slang-check-impl.h"

// This file constaints the semantic checking logic and
// related queries for declarations.
//
// Because declarations are the top-level construct
// of the AST (in turn containing all the statements,
// types, and expressions), the declaration-checking
// logic also orchestrates the overall flow and how
// and when things get checked.

#include "slang-lookup.h"

namespace Slang
{
        /// Visitor to transition declarations to `DeclCheckState::CheckedModifiers`
    struct SemanticsDeclModifiersVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclModifiersVisitor>
    {
        SemanticsDeclModifiersVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDeclGroup(DeclGroup*) {}
        
        void visitDecl(Decl* decl)
        {
            checkModifiers(decl);
        }
    };

    struct SemanticsDeclHeaderVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclHeaderVisitor>
    {
        SemanticsDeclHeaderVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDecl(Decl*) {}
        void visitDeclGroup(DeclGroup*) {}
       
        void checkVarDeclCommon(VarDeclBase* varDecl);

        void visitVarDecl(VarDecl* varDecl)
        {
            checkVarDeclCommon(varDecl);
        }

        void visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
        {
            checkVarDeclCommon(decl);
        }

        void visitImportDecl(ImportDecl* decl);

        void visitUsingDecl(UsingDecl* decl);

        void visitGenericTypeParamDecl(GenericTypeParamDecl* decl);

        void visitGenericValueParamDecl(GenericValueParamDecl* decl);

        void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl);

        void visitGenericDecl(GenericDecl* genericDecl);

        void visitTypeDefDecl(TypeDefDecl* decl);

        void visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl);

        void visitAssocTypeDecl(AssocTypeDecl* decl);

        void checkCallableDeclCommon(CallableDecl* decl);

        void visitFuncDecl(FuncDecl* funcDecl);

        void visitParamDecl(ParamDecl* paramDecl);

        void visitConstructorDecl(ConstructorDecl* decl);

        void visitAbstractStorageDeclCommon(ContainerDecl* decl);

        void visitSubscriptDecl(SubscriptDecl* decl);

        void visitPropertyDecl(PropertyDecl* decl);

        void visitStructDecl(StructDecl* decl);

            /// Get the type of the storage accessed by an accessor.
            ///
            /// The type of storage is determined by the parent declaration.
        Type* _getAccessorStorageType(AccessorDecl* decl);

            /// Perform checks common to all types of accessors.
        void _visitAccessorDeclCommon(AccessorDecl* decl);

        void visitAccessorDecl(AccessorDecl* decl);
        void visitSetterDecl(SetterDecl* decl);
    };

    struct SemanticsDeclRedeclarationVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclRedeclarationVisitor>
    {
        SemanticsDeclRedeclarationVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDecl(Decl*) {}
        void visitDeclGroup(DeclGroup*) {}

#define CASE(TYPE) void visit##TYPE(TYPE* decl) { checkForRedeclaration(decl); }

        CASE(FuncDecl)
        CASE(VarDeclBase)
        CASE(SimpleTypeDecl)
        CASE(AggTypeDecl)

#undef CASE
    };

    struct SemanticsDeclBasesVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclBasesVisitor>
    {
        SemanticsDeclBasesVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDecl(Decl*) {}
        void visitDeclGroup(DeclGroup*) {}

        void visitInheritanceDecl(InheritanceDecl* inheritanceDecl);

            /// Validate that `decl` isn't illegally inheriting from a type in another module.
            ///
            /// This call checks a single `inheritanceDecl` to make sure that it either
            ///     * names a base type from the same module as `decl`, or
            ///     * names a type that allows cross-module inheritance
        void _validateCrossModuleInheritance(
            AggTypeDeclBase* decl,
            InheritanceDecl* inheritanceDecl);

        void visitInterfaceDecl(InterfaceDecl* decl);

        void visitStructDecl(StructDecl* decl);

        void visitEnumDecl(EnumDecl* decl);

            /// Validate that the target type of an extension `decl` is valid.
        void _validateExtensionDeclTargetType(ExtensionDecl* decl);

        void visitExtensionDecl(ExtensionDecl* decl);
    };

    struct SemanticsDeclBodyVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclBodyVisitor>
    {
        SemanticsDeclBodyVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDecl(Decl*) {}
        void visitDeclGroup(DeclGroup*) {}

        void checkVarDeclCommon(VarDeclBase* varDecl);

        void visitVarDecl(VarDecl* varDecl)
        {
            checkVarDeclCommon(varDecl);
        }

        void visitGlobalGenericValueParamDecl(GlobalGenericValueParamDecl* decl)
        {
            checkVarDeclCommon(decl);
        }

        void visitEnumCaseDecl(EnumCaseDecl* decl);

        void visitEnumDecl(EnumDecl* decl);

        void visitFunctionDeclBase(FunctionDeclBase* funcDecl);

        void visitParamDecl(ParamDecl* paramDecl);
    };

        /// 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(as<ModuleDecl>(parentDecl))
            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(as<AggTypeDecl>(decl))
            return true;
        if(as<SimpleTypeDecl>(decl))
            return true;

        // Initializer/constructor declarations are effectively `static`
        // in Slang. They behave like functions that return an instance
        // of the enclosing type, rather than as functions that are
        // called on a pre-existing value.
        //
        if(as<ConstructorDecl>(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(as<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(as<TypeConstraintDecl>(decl))
            return false;

        return false;
    }

    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 = as<GenericDecl>(parentDecl))
            parentDecl = genericDecl->parentDecl;

        return isEffectivelyStatic(decl, parentDecl);
    }

        /// Is `decl` a global shader parameter declaration?
    bool isGlobalShaderParameter(VarDeclBase* decl)
    {
        // A global shader parameter must be declared at global or namespace
        // scope, so that it has a single definition across the module.
        //
        if(!as<NamespaceDeclBase>(decl->parentDecl)) return false;

        // A global variable marked `static` indicates a traditional
        // global variable (albeit one that is implicitly local to
        // the translation unit)
        //
        if(decl->hasModifier<HLSLStaticModifier>()) return false;

        // The `groupshared` modifier indicates that a variable cannot
        // be a shader parameters, but is instead transient storage
        // allocated for the duration of a thread-group's execution.
        //
        if(decl->hasModifier<HLSLGroupSharedModifier>()) return false;

        return true;
    }

    static bool _isLocalVar(VarDeclBase* varDecl)
    {
        auto pp = varDecl->parentDecl;

        if(as<ScopeDecl>(pp))
            return true;

        if(auto genericDecl = as<GenericDecl>(pp))
            pp = genericDecl;

        if(as<FuncDecl>(pp))
            return true;

        return false;
    }

    // Get the type to use when referencing a declaration
    QualType getTypeForDeclRef(
        ASTBuilder*             astBuilder,
        SemanticsVisitor*       sema,
        DiagnosticSink*         sink,
        DeclRef<Decl>           declRef,
        Type**           outTypeResult,
        SourceLoc               loc)
    {
        if( sema )
        {
            // 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.
            //
            // We detect the problematic case by looking for an attempt to reference
            // a local variable declaration when it is unchecked, or in the process
            // of being checked (the latter case catches a local variable that refers
            // to itself in its initial-value expression).
            //
            auto checkStateExt = declRef.getDecl()->checkState;
            if( checkStateExt.getState() == DeclCheckState::Unchecked
                || checkStateExt.isBeingChecked() )
            {
                if(auto varDecl = as<VarDecl>(declRef.getDecl()))
                {
                    if(_isLocalVar(varDecl))
                    {
                        sema->getSink()->diagnose(varDecl, Diagnostics::localVariableUsedBeforeDeclared, varDecl);
                        return QualType(astBuilder->getErrorType());
                    }
                }
            }

            // Once we've rules out the case of referencing a local declaration
            // before it has been checked, we will go ahead and ensure that
            // semantic checking has been performed on the chosen declaration,
            // at least up to the point where we can query its type.
            //
            sema->ensureDecl(declRef, DeclCheckState::CanUseTypeOfValueDecl);
        }

        // 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(astBuilder, varDeclRef);

            bool isLValue = true;
            if(varDeclRef.getDecl()->findModifier<ConstModifier>())
                isLValue = false;

            // Global-scope shader parameters should not be writable,
            // since they are effectively program inputs.
            //
            // TODO: We could eventually treat a mutable global shader
            // parameter as a shorthand for an immutable parameter and
            // a global variable that gets initialized from that parameter,
            // but in order to do so we'd need to support global variables
            // with resource types better in the back-end.
            //
            if(isGlobalShaderParameter(varDeclRef.getDecl()))
                isLValue = false;

            // Variables declared with `let` are always immutable.
            if(varDeclRef.is<LetDecl>())
                isLValue = false;

            // Generic value parameters are always immutable
            if(varDeclRef.is<GenericValueParamDecl>())
                isLValue = false;

            // Function parameters declared in the "modern" style
            // are immutable unless they have an `out` or `inout` modifier.
            if(varDeclRef.is<ModernParamDecl>())
            {
                // Note: the `inout` modifier AST class inherits from
                // the class for the `out` modifier so that we can
                // make simple checks like this.
                //
                if( !varDeclRef.getDecl()->hasModifier<OutModifier>() )
                {
                    isLValue = false;
                }
            }

            qualType.isLeftValue = isLValue;
            return qualType;
        }
        else if( auto propertyDeclRef = declRef.as<PropertyDecl>() )
        {
            // Access to a declared `property` is similar to
            // access to a variable/field, except that it
            // is mediated through accessors (getters, seters, etc.).

            QualType qualType;
            qualType.type = getType(astBuilder, propertyDeclRef);

            bool isLValue = false;

            // If the property has any declared accessors that
            // can be used to set the property, then the resulting
            // expression behaves as an l-value.
            //
            if(propertyDeclRef.getDecl()->getMembersOfType<SetterDecl>().isNonEmpty())
                isLValue = true;
            if(propertyDeclRef.getDecl()->getMembersOfType<RefAccessorDecl>().isNonEmpty())
                isLValue = true;

            qualType.isLeftValue = isLValue;
            return qualType;

        }
        else if( auto enumCaseDeclRef = declRef.as<EnumCaseDecl>() )
        {
            QualType qualType;
            qualType.type = getType(astBuilder, enumCaseDeclRef);
            qualType.isLeftValue = false;
            return qualType;
        }
        else if (auto typeAliasDeclRef = declRef.as<TypeDefDecl>())
        {
            auto type = getNamedType(astBuilder, typeAliasDeclRef);
            *outTypeResult = type;
            return QualType(astBuilder->getTypeType(type));
        }
        else if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>())
        {
            auto type = DeclRefType::create(astBuilder, aggTypeDeclRef);
            *outTypeResult = type;
            return QualType(astBuilder->getTypeType(type));
        }
        else if (auto simpleTypeDeclRef = declRef.as<SimpleTypeDecl>())
        {
            auto type = DeclRefType::create(astBuilder, simpleTypeDeclRef);
            *outTypeResult = type;
            return QualType(astBuilder->getTypeType(type));
        }
        else if (auto genericDeclRef = declRef.as<GenericDecl>())
        {
            auto type = getGenericDeclRefType(astBuilder, genericDeclRef);
            *outTypeResult = type;
            return QualType(astBuilder->getTypeType(type));
        }
        else if (auto funcDeclRef = declRef.as<CallableDecl>())
        {
            auto type = getFuncType(astBuilder, 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(astBuilder, constraintDeclRef);
            return QualType(type);
        }
        else if( auto namespaceDeclRef = declRef.as<NamespaceDeclBase>())
        {
            auto type = getNamespaceType(astBuilder, namespaceDeclRef);
            return QualType(type);
        }
        if( sink )
        {
            // The compiler is trying to form a reference to a declaration
            // that doesn't appear to be usable as an expression or type.
            //
            // In practice, this arises when user code has an undefined-identifier
            // error, but the name that was undefined in context also matches
            // a contextual keyword. Rather than confuse the user with the
            // details of contextual keywords in the compiler, we will diagnose
            // this as an undefined identifier.
            //
            // TODO: This code could break if we ever go down this path with
            // an identifier that doesn't have a name.
            //
            sink->diagnose(loc, Diagnostics::undefinedIdentifier2, declRef.getName());
        }
        return QualType(astBuilder->getErrorType());
    }

    QualType getTypeForDeclRef(
        ASTBuilder*     astBuilder, 
        DeclRef<Decl>   declRef,
        SourceLoc       loc)
    {
        Type* typeResult = nullptr;
        return getTypeForDeclRef(astBuilder, nullptr, nullptr, declRef, &typeResult, loc);
    }

    DeclRef<ExtensionDecl> ApplyExtensionToType(
        SemanticsVisitor*       semantics,
        ExtensionDecl*          extDecl,
        Type*  type)
    {
        if(!semantics)
            return DeclRef<ExtensionDecl>();

        return semantics->ApplyExtensionToType(extDecl, type);
    }

    GenericSubstitution* createDefaultSubstitutionsForGeneric(
        ASTBuilder*             astBuilder, 
        GenericDecl*            genericDecl,
        Substitutions*   outerSubst)
    {
        GenericSubstitution* genericSubst = astBuilder->create<GenericSubstitution>();
        genericSubst->genericDecl = genericDecl;
        genericSubst->outer = outerSubst;

        for( auto mm : genericDecl->members )
        {
            if( auto genericTypeParamDecl = as<GenericTypeParamDecl>(mm) )
            {
                genericSubst->args.add(DeclRefType::create(astBuilder, DeclRef<Decl>(genericTypeParamDecl, outerSubst)));
            }
            else if( auto genericValueParamDecl = as<GenericValueParamDecl>(mm) )
            {
                genericSubst->args.add(astBuilder->create<GenericParamIntVal>(DeclRef<GenericValueParamDecl>(genericValueParamDecl, outerSubst)));
            }
        }

        // create default substitution arguments for constraints
        for (auto mm : genericDecl->members)
        {
            if (auto genericTypeConstraintDecl = as<GenericTypeConstraintDecl>(mm))
            {
                DeclaredSubtypeWitness* witness = astBuilder->create<DeclaredSubtypeWitness>();
                witness->declRef = DeclRef<Decl>(genericTypeConstraintDecl, 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(
        ASTBuilder*     astBuilder, 
        Decl*           decl,
        SubstitutionSet outerSubstSet)
    {
        auto dd = decl->parentDecl;
        if( auto genericDecl = as<GenericDecl>(dd) )
        {
            // We don't want to specialize references to anything
            // other than the "inner" declaration itself.
            if(decl != genericDecl->inner)
                return outerSubstSet;

            GenericSubstitution* genericSubst = createDefaultSubstitutionsForGeneric(
                astBuilder,
                genericDecl,
                outerSubstSet.substitutions);

            return SubstitutionSet(genericSubst);
        }

        return outerSubstSet;
    }

    SubstitutionSet createDefaultSubstitutions(
        ASTBuilder* astBuilder, 
        Decl*   decl)
    {
        SubstitutionSet subst;
        if( auto parentDecl = decl->parentDecl )
        {
            subst = createDefaultSubstitutions(astBuilder, parentDecl);
        }
        subst = createDefaultSubstitutions(astBuilder, decl, subst);
        return subst;
    }

    void ensureDecl(SemanticsVisitor* visitor, Decl* decl, DeclCheckState state)
    {
        visitor->ensureDecl(decl, state);
    }

    bool SemanticsVisitor::isDeclUsableAsStaticMember(
        Decl*   decl)
    {
        if(auto genericDecl = as<GenericDecl>(decl))
            decl = genericDecl->inner;

        if(decl->hasModifier<HLSLStaticModifier>())
            return true;

        if(as<ConstructorDecl>(decl))
            return true;

        if(as<EnumCaseDecl>(decl))
            return true;

        if(as<AggTypeDeclBase>(decl))
            return true;

        if(as<SimpleTypeDecl>(decl))
            return true;

        if(as<TypeConstraintDecl>(decl))
            return true;

        return false;
    }

    bool SemanticsVisitor::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);
    }

        /// Dispatch an appropriate visitor to check `decl` up to state `state`
        ///
        /// The current state of `decl` must be `state-1`.
        /// This call does *not* handle updating the state of `decl`; the
        /// caller takes responsibility for doing so.
        ///
    static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared);

    // 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 SemanticsVisitor::ensureDecl(Decl* decl, DeclCheckState state)
    {
        // If the `decl` has already been checked up to or beyond `state`
        // then there is nothing for us to do.
        //
        if (decl->isChecked(state)) return;

        // Is the declaration already being checked, somewhere up the
        // call stack from us?
        //
        if(decl->checkState.isBeingChecked())
        {
            // 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.
            //
            getSink()->diagnose(decl, Diagnostics::cyclicReference, decl);
            return;
        }

        // Set the flag that indicates we are checking this declaration,
        // so that the cycle check above will catch us before we go
        // into any infinite loops.
        //
        decl->checkState.setIsBeingChecked(true);

        // Our task is to bring the `decl` up to `state` which may be
        // one or more steps ahead of where it currently is. We can
        // invoke a visitor designed to bring a declaration from state
        // N to state N+1, and in general we might need multiple such
        // passes to get `decl` to where we need it.
        //
        // The coding of this loop is somewhat defensive to deal
        // with special cases that will be described along the way.
        //
        for(;;)
        {
            // The first thing is to check what state the decl is
            // currently in at the start of this loop iteration,
            // and to bail out if it has been checked up to
            // (or beyond) our target state.
            //
            auto currentState = decl->checkState.getState();
            if(currentState >= state)
                break;

            // Because our visitors are only designed to go from state
            // N to N+1 in general, we will aspire to transition to
            // a state that is one greater than `currentState`.
            //
            auto nextState = DeclCheckState(Int(currentState) + 1);

            // We now dispatch an appropriate visitor based on `nextState`.
            //
            _dispatchDeclCheckingVisitor(decl, nextState, getShared());

            // In the common case, the visitor will have done the necessary
            // checking, but will *not* have updated the `checkState` on
            // `decl`. In that case we will do the update here, to save
            // us the complication of having to deal with state update in
            // every single visitor method.
            //
            // However, sometimes a visitor *will* want to manually update
            // the state of a declaration, and it may actually update it
            // *past* the `nextState` we asked for (or even past the
            // eventual target `state`). In those cases we don't want to
            // accidentally set the state of `decl` to something lower
            // than what has actually been checked, so we test for
            // such cases here.
            //
            if(nextState > decl->checkState.getState())
            {
                decl->setCheckState(nextState);
            }
        }

        // Once we are done here, the state of `decl` should have
        // been upgraded to (at least) `state`.
        //
        SLANG_ASSERT(decl->isChecked(state));

        // Now that we are done checking `decl` we need to restore
        // its "is being checked" flag so that we don't generate
        // errors the next time somebody calls `ensureDecl()` on it.
        //
        decl->checkState.setIsBeingChecked(false);
    }

        /// Recursively ensure the tree of declarations under `decl` is in `state`.
        ///
        /// This function does *not* handle declarations nested in function bodies
        /// because those cannot be meaningfully checked outside of the context
        /// of their surrounding statement(s).
        ///
    static void _ensureAllDeclsRec(
        SemanticsDeclVisitorBase*   visitor,
        Decl*                       decl,
        DeclCheckState              state)
    {
        // Ensure `decl` itself first.
        visitor->ensureDecl(decl, state);

        // If `decl` is a container, then we want to ensure its children.
        if(auto containerDecl = as<ContainerDecl>(decl))
        {            
            // NOTE! We purposefully do not iterate with the for(auto childDecl : containerDecl->members) here,
            // because the visitor may add to `members` whilst iteration takes place, invalidating the iterator
            // and likely a crash.
            // 
            // Accessing the members via index side steps the issue.
            const auto& members = containerDecl->members;
            for(Index i = 0; i < members.getCount(); ++i)
            {
                Decl* childDecl = members[i];

                // As an exception, if any of the child is a `ScopeDecl`,
                // then that indicates that it represents a scope for local
                // declarations under a statement (e.g., in a function body),
                // and we don't want to check such local declarations here.
                //

                if(as<ScopeDecl>(childDecl))
                    continue;

                _ensureAllDeclsRec(visitor, childDecl, state);
            }
        }

        // Note: the "inner" declaration of a `GenericDecl` is currently
        // not exposed as one of its children (despite a `GenericDecl`
        // being a `ContainerDecl`), so we need to handle the inner
        // declaration of a generic as another case here.
        //
        if(auto genericDecl = as<GenericDecl>(decl))
        {
            _ensureAllDeclsRec(visitor, genericDecl->inner, state);
        }
    }

    static bool isUnsizedArrayType(Type* type)
    {
        // Not an array?
        auto arrayType = as<ArrayExpressionType>(type);
        if (!arrayType) return false;

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

        return true;
    }

    void SemanticsVisitor::_validateCircularVarDefinition(VarDeclBase* varDecl)
    {
        // The easiest way to test if the declaration is circular is to
        // validate it as a constant.
        //
        // TODO: The logic here will only apply for `static const` declarations
        // of integer type, given that our constant folding currently only
        // applies to such types. A more robust fix would involve a truly
        // recursive walk of the AST declarations, and an even *more* robust
        // fix would wait until after IR linking to detect and diagnose circularity
        // in case it crosses module boundaries.
        //
        //
        if(!isScalarIntegerType(varDecl->type))
            return;
        tryConstantFoldDeclRef(DeclRef<VarDeclBase>(varDecl, nullptr), nullptr);
    }

    void SemanticsDeclHeaderVisitor::checkVarDeclCommon(VarDeclBase* varDecl)
    {
        // A variable that didn't have an explicit type written must
        // have its type inferred from the initial-value expression.
        //
        if(!varDecl->type.exp)
        {
            // In this case we need to perform all checking of the
            // variable (including semantic checking of the initial-value
            // expression) during the first phase of checking.

            auto initExpr = varDecl->initExpr;
            if(!initExpr)
            {
                getSink()->diagnose(varDecl, Diagnostics::varWithoutTypeMustHaveInitializer);
                varDecl->type.type = m_astBuilder->getErrorType();
            }
            else
            {
                initExpr = CheckExpr(initExpr);

                // TODO: We might need some additional steps here to ensure
                // that the type of the expression is one we are okay with
                // inferring. E.g., if we ever decide that integer and floating-point
                // literals have a distinct type from the standard int/float types,
                // then we would need to "decay" a literal to an explicit type here.

                varDecl->initExpr = initExpr;
                varDecl->type.type = initExpr->type;

                _validateCircularVarDefinition(varDecl);
            }

            // If we've gone down this path, then the variable
            // declaration is actually pretty far along in checking
            varDecl->setCheckState(DeclCheckState::Checked);
        }
        else
        {
            // A variable with an explicit type is simpler, for the
            // most part.

            TypeExp typeExp = CheckUsableType(varDecl->type);
            varDecl->type = typeExp;
            if (varDecl->type.equals(m_astBuilder->getVoidType()))
            {
                getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
            }

            // If this is an unsized 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...
            //
            if(isUnsizedArrayType(varDecl->type))
            {
                if (auto initExpr = varDecl->initExpr)
                {
                    initExpr = CheckTerm(initExpr);
                    initExpr = coerce(varDecl->type.Ptr(), initExpr);
                    varDecl->initExpr = initExpr;

                    maybeInferArraySizeForVariable(varDecl);

                    varDecl->setCheckState(DeclCheckState::Checked);
                }
            }
            //
            // 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);
        }

        // The NVAPI library allows user code to express extended operations
        // (not supported natively by D3D HLSL) by communicating with
        // a specially identified shader parameter called `g_NvidiaExt`.
        //
        // By default, that shader parameter would look like an ordinary
        // global shader parameter to Slang, but we want to be able to
        // associate special behavior with it to make downstream compilation
        // work nicely (especially in the case where certain cross-platform
        // operations in the Slang standard library need to use NVAPI).
        //
        // We will detect a global variable declaration that appears to
        // be declaring `g_NvidiaExt` from NVAPI, and mark it with a special
        // modifier to allow downstream steps to detect it whether or
        // not it has an associated name.
        //
        if( as<ModuleDecl>(varDecl->parentDecl)
            && varDecl->getName()
            && varDecl->getName()->text == "g_NvidiaExt" )
        {
            addModifier(varDecl, m_astBuilder->create<NVAPIMagicModifier>());
        }
        //
        // One thing that the `NVAPIMagicModifier` is going to do is ensure
        // that `g_NvidiaExt` always gets emitted with *exactly* that name,
        // whether or not obfuscation or other steps are enabled.
        //
        // The `g_NvidiaExt` variable is declared as a:
        //
        //      RWStructuredBuffer<NvShaderExtnStruct>
        //
        // and we also want to make sure that the fields of that struct
        // retain their original names in output code. We will detect
        // variable declarations that represent fields of that struct
        // and flag them as "magic" as well.
        //
        // Note: The goal here is to make it so that generated HLSL output
        // can either use these declarations as they have been preocessed
        // by the Slang front-end *or* they can use declarations directly
        // from the NVAPI header during downstream compilation.
        //
        // TODO: It would be nice if we had a way to identify *all* of the
        // declarations that come from the NVAPI header and mark them, so
        // that the Slang front-end doesn't have to take responsibility
        // for generating code from them (and can instead rely on the downstream
        // compiler alone).
        //
        // The NVAPI header doesn't put any kind of macro-defined modifier
        // (defaulting to an empty macro) in front of its declarations,
        // so the most plausible way to add a modifier to all the declarations
        // would be to tag the `nvHLSLExtns.h` header in a list of "magic"
        // headers which should get all their declarations flagged during
        // front-end processing, and then use the same header again during
        // downstream compilation.
        //
        // For now, the current hackery seems a bit less complicated.
        //
        if( auto structDecl = as<StructDecl>(varDecl->parentDecl))
        {
            if( structDecl->getName()
                && structDecl->getName()->text == "NvShaderExtnStruct" )
            {
                addModifier(varDecl, m_astBuilder->create<NVAPIMagicModifier>());
            }
        }
    }

    void SemanticsDeclHeaderVisitor::visitStructDecl(StructDecl* structDecl)
    {
        // As described above in `SemanticsDeclHeaderVisitor::checkVarDeclCommon`,
        // we want to identify and tag the "magic" declarations that make NVAPI
        // work, so that downstream passes can identify them and act accordingly.
        //
        // In this case, we are looking for the `NvShaderExtnStruct` type, which
        // is used by `g_NvidiaExt`.
        //
        if( structDecl->getName()
            && structDecl->getName()->text == "NvShaderExtnStruct" )
        {
            addModifier(structDecl, m_astBuilder->create<NVAPIMagicModifier>());
        }
    }

    void SemanticsDeclBodyVisitor::checkVarDeclCommon(VarDeclBase* varDecl)
    {
        if (auto initExpr = varDecl->initExpr)
        {
            // If the variable has an explicit initial-value expression,
            // then we simply need to check that expression and coerce
            // it to the type of the variable.
            //
            initExpr = CheckTerm(initExpr);
            initExpr = coerce(varDecl->type.Ptr(), initExpr);
            varDecl->initExpr = initExpr;

            // We need to ensure that any variable doesn't introduce
            // a constant with a circular definition.
            //
            varDecl->setCheckState(DeclCheckState::Checked);
            _validateCircularVarDefinition(varDecl);
        }
        else
        {
            // If a variable doesn't have an explicit initial-value
            // expression, it is still possible that it should
            // be initialized implicitly, because the type of the
            // variable has a default (zero parameter) initializer.
            // That is, for types where it is possible, we will
            // treat a variable declared like this:
            //
            //      MyType myVar;
            //
            // as if it were declared as:
            //
            //      MyType myVar = MyType();
            //
            // Rather than try to code up an ad hoc search for an
            // appropriate initializer here, we will instead fall
            // back on the general-purpose overload-resolution
            // machinery, which can handle looking up initializers
            // and filtering them to ones that are applicable
            // to our "call site" with zero arguments.
            //
            auto type = varDecl->getType();

            OverloadResolveContext overloadContext;
            overloadContext.loc = varDecl->nameAndLoc.loc;
            overloadContext.mode = OverloadResolveContext::Mode::JustTrying;
            AddTypeOverloadCandidates(type, overloadContext);

            if(overloadContext.bestCandidates.getCount() != 0)
            {
                // If there were multiple equally-good candidates to call,
                // then might have an ambiguity.
                //
                // Before issuing any kind of diagnostic we need to check
                // if any of those candidates are actually applicable,
                // because if they aren't then we actually just have
                // an uninitialized varaible.
                //
                if(overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Applicable)
                    return;

                getSink()->diagnose(varDecl, Diagnostics::ambiguousDefaultInitializerForType, type);
            }
            else if(overloadContext.bestCandidate)
            {
                // If we are in the single-candidate case, then we again
                // want to ignore the case where that candidate wasn't
                // actually applicable, because declaring a variable
                // of a type that *doesn't* have a default initializer
                // isn't actually an error.
                //
                if(overloadContext.bestCandidate->status != OverloadCandidate::Status::Applicable)
                    return;

                // If we had a single best candidate *and* it was applicable,
                // then we use it to construct a new initial-value expression
                // for the variable, that will be used for all downstream
                // code generation.
                //
                varDecl->initExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate);
            }
        }
    }

    // Fill in default substitutions for the 'subtype' part of a type constraint decl
    void SemanticsVisitor::CheckConstraintSubType(TypeExp& typeExp)
    {
        if (auto sharedTypeExpr = as<SharedTypeExpr>(typeExp.exp))
        {
            if (auto declRefType = as<DeclRefType>(sharedTypeExpr->base))
            {
                declRefType->declRef.substitutions = createDefaultSubstitutions(m_astBuilder, declRefType->declRef.getDecl());

                if (auto typetype = as<TypeType>(typeExp.exp->type))
                    typetype->type = declRefType;
            }
        }
    }

    void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(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.
        //
        CheckConstraintSubType(decl->sub);
        decl->sub = TranslateTypeNodeForced(decl->sub);
        decl->sup = TranslateTypeNodeForced(decl->sup);
    }

    void SemanticsDeclHeaderVisitor::visitGenericTypeParamDecl(GenericTypeParamDecl* decl)
    {
        // TODO: could probably push checking the default value
        // for a generic type parameter later.
        //
        decl->initType = CheckProperType(decl->initType);
    }

    void SemanticsDeclHeaderVisitor::visitGenericValueParamDecl(GenericValueParamDecl* decl)
    {
        checkVarDeclCommon(decl);
    }

    void SemanticsDeclHeaderVisitor::visitGenericDecl(GenericDecl* genericDecl)
    {
        genericDecl->setCheckState(DeclCheckState::ReadyForLookup);

        // NOTE! We purposefully do not iterate with the for(auto m : genericDecl->members) here,
        // because the visitor may add to `members` whilst iteration takes place, invalidating the iterator
        // and likely a crash.
        // 
        // Accessing the members via index side steps the issue.
        const auto& members = genericDecl->members;
        for (Index i = 0; i < members.getCount(); ++i)
        {
            Decl* m = members[i];

            if (auto typeParam = as<GenericTypeParamDecl>(m))
            {
                ensureDecl(typeParam, DeclCheckState::ReadyForReference);
            }
            else if (auto valParam = as<GenericValueParamDecl>(m))
            {
                ensureDecl(valParam, DeclCheckState::ReadyForReference);
            }
            else if (auto constraint = as<GenericTypeConstraintDecl>(m))
            {
                ensureDecl(constraint, DeclCheckState::ReadyForReference);
            }
        }
    }

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

        // Note: we do not check whether the type being inherited from
        // is valid to use for inheritance here, because there could
        // be contextual factors that need to be taken into account
        // based on the declaration that is doing the inheriting.
    }

        // Concretize interface conformances so that we have witnesses as required for lookup.
        // for lookup.
    struct SemanticsDeclConformancesVisitor
        : public SemanticsDeclVisitorBase
        , public DeclVisitor<SemanticsDeclConformancesVisitor>
    {
        SemanticsDeclConformancesVisitor(SharedSemanticsContext* shared)
            : SemanticsDeclVisitorBase(shared)
        {}

        void visitDecl(Decl*) {}
        void visitDeclGroup(DeclGroup*) {}

        // Any user-defined type may have declared interface conformances,
        // which we should check.
        //
        void visitAggTypeDecl(AggTypeDecl* aggTypeDecl)
        {
            checkAggTypeConformance(aggTypeDecl);
        }

        // Conformances can also come via `extension` declarations, and
        // we should check them against the type(s) being extended.
        //
        void visitExtensionDecl(ExtensionDecl* extensionDecl)
        {
            checkExtensionConformance(extensionDecl);
        }
    };

        /// Recursively register any builtin declarations that need to be attached to the `session`.
        ///
        /// This function should only be needed for declarations in the standard library.
        ///
    static void _registerBuiltinDeclsRec(Session* session, Decl* decl)
    {
        SharedASTBuilder* sharedASTBuilder = session->m_sharedASTBuilder;

        if (auto builtinMod = decl->findModifier<BuiltinTypeModifier>())
        {
            sharedASTBuilder->registerBuiltinDecl(decl, builtinMod);
        }
        if (auto magicMod = decl->findModifier<MagicTypeModifier>())
        {
            sharedASTBuilder->registerMagicDecl(decl, magicMod);
        }

        if(auto containerDecl = as<ContainerDecl>(decl))
        {
            for(auto childDecl : containerDecl->members)
            {
                if(as<ScopeDecl>(childDecl))
                    continue;

                _registerBuiltinDeclsRec(session, childDecl);
            }
        }
        if(auto genericDecl = as<GenericDecl>(decl))
        {
            _registerBuiltinDeclsRec(session, genericDecl->inner);
        }
    }

    void registerBuiltinDecls(Session* session, Decl* decl)
    {
        _registerBuiltinDeclsRec(session, decl);
    }

    void SemanticsDeclVisitorBase::checkModule(ModuleDecl* moduleDecl)
    {
        // When we are dealing with code from the standard library,
        // there is a potential problem where we might need to look
        // up built-in types like `Int` through the session (e.g.,
        // to determine the type for an integer literal), but those
        // types might not have been registered yet. We solve that
        // by doing a pre-process on standard-library code to find
        // and register any built-in declarations.
        //
        // TODO: This could be factored into another visitor pass
        // that fits the more standard checking below, but that would
        // seemingly add overhead to checking things other than
        // the standard library.
        //
        if(isFromStdLib(moduleDecl))
        {
            _registerBuiltinDeclsRec(getSession(), moduleDecl);
        }

        // We need/want to visit any `import` declarations before
        // anything else, to make sure that scoping works.
        //
        // TODO: This could be factored into another visitor pass
        // that fits more with the standard checking below.
        //
        for(auto importDecl : moduleDecl->getMembersOfType<ImportDecl>())
        {
            ensureDecl(importDecl, DeclCheckState::Checked);
        }

        // The entire goal of semantic checking is to get all of the
        // declarations in the module up to `DeclCheckState::Checked`.
        //
        // The main catch is that checking one declaration A up to state M
        // may required that declaration B is checked up to state N.
        // A call to `ensureDecl(B, N)` can guarantee that things are checked
        // when and where we need them, but that runs the risk of creating
        // very deep recursion in the semantic checking.
        //
        // Instead, we would rather do more breadth-first checking,
        // where everything gets checked up to state 1, 2, ...
        // before anything gets too far ahead.
        // We will therefore enumerate the states/phases for checking,
        // and then iteratively try to update all declarations to each
        // state in turn.
        //
        // Note: for a simpler language we could eliminate `ensureDecl`
        // completely and *just* have these phases of checking.
        // Unfortunately, we have some circularity between the phases:
        //
        // * Checking an overloaded call requires knowing the parameter
        //   types of all candidate callees.
        //
        // * Checking the parameter type of a function requires being
        //   able to check type expressions.
        //
        // * A type expression like `vector<T, N>` may have an arbitary
        //   expression for `N`.
        //
        // * An arbitrary expression may include function calls, which
        //   may be to overloaded functions.
        //
        // Languages like C++ solve the apparent problem by making
        // restrictions on order of declaration/definition (and by
        // requiring forward declarations or the `template`/`typename`
        // keywrods in some cases).
        //
        // TODO: We could eventually eliminate the potential recursion
        // in checking by splitting each phase into a "requirements gathering"
        // step and an actual execution step.
        //
        // When checking a declaration D up to state S, the requirements
        // gathering step would produce a list of pairs `(someDecl, someState)`
        // indicating that `someDecl` must be in `someState` before the
        // actual execution of checking for `(D,S)` can proceeed. The checker
        // can then produce an elaborated dependency graph and select nodes
        // for execution in an order that satisfies all the dependencies.
        //
        // Such a more elaborate checking scheme will have to wait for another
        // day, but might be worth it (or even necessary) if/when we want to
        // support incremental compilation.
        //
        DeclCheckState states[] =
        {
            DeclCheckState::ModifiersChecked,
            DeclCheckState::ReadyForReference,
            DeclCheckState::ReadyForLookup,
            DeclCheckState::ReadyForLookup,
            DeclCheckState::Checked
        };
        for(auto s : states)
        {
            // When advancing to state `s` we will recursively
            // advance all declarations rooted in the module
            // up to `s`.
            //
            // TODO: In cases where a large module is split across files,
            // we could potentially parallelize front-end compilation by
            // having multiple instances of the front end where each is
            // only responsible for those declarations in a given file.
            //
            // Under that model, we might only apply later phases of
            // checking (notably the final push to `DeclState::Checked`)
            // to the subset of declarations coming from a given source
            // file.
            //
            _ensureAllDeclsRec(this, moduleDecl, s);
        }

        // Once we have completed the above loop, all declarations not
        // nested in function bodies should be in `DeclState::Checked`.
        // Furthermore, because a fully checked function will have checked
        // its body, this also means that all function bodies and the
        // declarations they contain should be fully checked.
    }

    bool SemanticsVisitor::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;
        }

        // A signature matches the required one if it has the right number of parameters,
        // and those parameters have the right types, and also the result/return type
        // is the required one.
        //
        auto requiredParams = getParameters(requiredMemberDeclRef).toArray();
        auto satisfyingParams = getParameters(satisfyingMemberDeclRef).toArray();
        auto paramCount = requiredParams.getCount();
        if(satisfyingParams.getCount() != paramCount)
            return false;

        for(Index paramIndex = 0; paramIndex < paramCount; ++paramIndex)
        {
            auto requiredParam = requiredParams[paramIndex];
            auto satisfyingParam = satisfyingParams[paramIndex];

            auto requiredParamType = getType(m_astBuilder, requiredParam);
            auto satisfyingParamType = getType(m_astBuilder, satisfyingParam);

            if(!requiredParamType->equals(satisfyingParamType))
                return false;
        }

        auto requiredResultType = getResultType(m_astBuilder, requiredMemberDeclRef);
        auto satisfyingResultType = getResultType(m_astBuilder, satisfyingMemberDeclRef);
        if(!requiredResultType->equals(satisfyingResultType))
            return false;

        witnessTable->add(
            requiredMemberDeclRef.getDecl(),
            RequirementWitness(satisfyingMemberDeclRef));
        return true;
    }

    bool SemanticsVisitor::doesAccessorMatchRequirement(
        DeclRef<AccessorDecl>   satisfyingMemberDeclRef,
        DeclRef<AccessorDecl>   requiredMemberDeclRef)
    {
        // We require the AST node class of the satisfying accessor
        // to be a subclass of the one from the required accessor.
        //
        // For our current accessor types, this amounts to requiring
        // an exact match, but using a subtype test means that if
        // we ever add an `ExtraSpecialGetDecl` that is a subclass
        // of `GetDecl`, then one of those would be able to satisfy
        // a `get` requirement.
        //
        auto satisfyingMemberClass = satisfyingMemberDeclRef.getDecl()->getClass();
        auto requiredMemberClass = requiredMemberDeclRef.getDecl()->getClass();
        if(!satisfyingMemberClass.isSubClassOfImpl(requiredMemberClass))
            return false;

        // We do not check the parameters or return types of accessors
        // here, under the assumption that the validity checks for
        // the parent `property` declaration would already make sure
        // they are in order.

        // TODO: There are other checks we need to make here, like not letting
        // an ordinary `set` satisfy a `[nonmutating] set` requirement.

        return true;
    }

    bool SemanticsVisitor::doesPropertyMatchRequirement(
        DeclRef<PropertyDecl>   satisfyingMemberDeclRef,
        DeclRef<PropertyDecl>   requiredMemberDeclRef,
        RefPtr<WitnessTable>    witnessTable)
    {
        // The type of the satisfying member must match the type of the required member.
        //
        // Note: It is possible that a `get`-only property could be satisfied by
        // a declaration that uses a subtype of the requirement, but that would not
        // count as an "exact match" and we would rely on the logic to synthesize
        // a stub implementation in that case.
        //
        auto satisfyingType = getType(getASTBuilder(), satisfyingMemberDeclRef);
        auto requiredType = getType(getASTBuilder(), requiredMemberDeclRef);
        if(!satisfyingType->equals(requiredType))
            return false;

        // Each accessor in the requirement must be accounted for by an accessor
        // in the satisfying member.
        //
        // Note: it is fine for the satisfying member to provide *more* accessors
        // than the original declaration.
        //
        Dictionary<DeclRef<AccessorDecl>, DeclRef<AccessorDecl>> mapRequiredToSatisfyingAccessorDeclRef;
        for( auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(requiredMemberDeclRef) )
        {
            // We need to search for an accessor that can satisfy the requirement.
            //
            // For now we will do the simplest (and slowest) thing of a linear search,
            // which is mostly fine because the number of accessors is bounded.
            //
            bool found = false;
            for( auto satisfyingAccessorDeclRef : getMembersOfType<AccessorDecl>(satisfyingMemberDeclRef) )
            {
                if( doesAccessorMatchRequirement(satisfyingAccessorDeclRef, requiredAccessorDeclRef) )
                {
                    // When we find a match on an accessor, we record it so that
                    // we can set up the witness values later, but we do *not*
                    // record it into the actual witness table yet, in case
                    // a later accessor comes along that doesn't find a match.
                    //
                    mapRequiredToSatisfyingAccessorDeclRef.Add(requiredAccessorDeclRef, satisfyingAccessorDeclRef);
                    found = true;
                    break;
                }
            }
            if(!found)
                return false;
        }

        // Once things are done, we will install the satisfying values
        // into the witness table for the requirements.
        //
        for( auto p : mapRequiredToSatisfyingAccessorDeclRef )
        {
            witnessTable->add(
                p.Key,
                RequirementWitness(p.Value));
        }
        //
        // Note: the property declaration itself isn't something that
        // has a useful value/representation in downstream passes, so
        // we are mostly just installing it into the witness table
        // as a way to mark this requirement as being satisfied.
        //
        // TODO: It is possible that having a witness table entry that
        // doesn't actually map to any IR value could create a problem
        // in downstream passes. If such propblems arise, we should
        // probably create a new `RequirementWitness` case that
        // represents a witness value that is only needed by the front-end,
        // and that can be ignored by IR and emit logic.
        //
        witnessTable->add(
            requiredMemberDeclRef.getDecl(),
            RequirementWitness(satisfyingMemberDeclRef));
        return true;
    }


    bool SemanticsVisitor::doesGenericSignatureMatchRequirement(
        DeclRef<GenericDecl>        satisfyingGenericDeclRef,
        DeclRef<GenericDecl>        requiredGenericDeclRef,
        RefPtr<WitnessTable>        witnessTable)
    {
        // The signature of a generic is defiend by its members, and we need the
        // satisfying value to have the same number of members for it to be an
        // exact match.
        //
        auto memberCount = requiredGenericDeclRef.getDecl()->members.getCount();
        if(satisfyingGenericDeclRef.getDecl()->members.getCount() != memberCount)
            return false;

        // We then want to check that pairwise members match, in order.
        //
        auto requiredMemberDeclRefs = getMembers(requiredGenericDeclRef);
        auto satisfyingMemberDeclRefs = getMembers(satisfyingGenericDeclRef);
        //
        // We start by performing a superficial "structural" match of the parameters
        // to ensure that the two generics have an equivalent mix of type, value,
        // and constraint parameters in the same order.
        //
        // Note that in this step we do *not* make any checks on the actual types
        // involved in constraints, or on the types of value parameters. The reason
        // for this is that the types on those parameters could be dependent on
        // type parameters in the generic parameter list, and thus there could be
        // a mismatch at this point. For example, if we have:
        //
        //      interface IBase         { void doThing<T, U : IThing<T>>(); }
        //      struct Derived : IBase  { void doThing<X, Y : IThing<X>>(); }
        //
        // We clearly have a signature match here, but the constraint parameters for
        // `U : IThing<T>` and `Y : IThing<X>` have the problem that both the sub-type
        // and super-type they reference are not equivalent without substititions.
        //
        // We will deal with this issue after the structural matching is checked, at
        // which point we can actually verify things like types.
        //
        for (Index i = 0; i < memberCount; i++)
        {
            auto requiredMemberDeclRef = requiredMemberDeclRefs[i];
            auto satisfyingMemberDeclRef = satisfyingMemberDeclRefs[i];

            if (as<GenericTypeParamDecl>(requiredMemberDeclRef))
            {
                if (as<GenericTypeParamDecl>(satisfyingMemberDeclRef))
                {
                }
                else
                    return false;
            }
            else if (auto requiredValueParamDeclRef = requiredMemberDeclRef.as<GenericValueParamDecl>())
            {
                if (auto satisfyingValueParamDeclRef = satisfyingMemberDeclRef.as<GenericValueParamDecl>())
                {
                }
                else
                    return false;
            }
            else if (auto requiredConstraintDeclRef = requiredMemberDeclRef.as<GenericTypeConstraintDecl>())
            {
                if (auto satisfyingConstraintDeclRef = satisfyingMemberDeclRef.as<GenericTypeConstraintDecl>())
                {
                }
                else
                    return false;
            }
        }

        // In order to compare the inner declarations of the two generics, we need to
        // align them so that they are expressed in terms of consistent type parameters.
        //
        // For example, we might have:
        //
        //      interface IBase           { void doThing<T>(T val); }
        //      struct    Derived : IBase { void doThing<U>(U val); }
        //
        // If we directly compare the signatures of the inner `doThing` function declarations,
        // we'd find a mismatch between the `T` and `U` types of the `val` parameter.
        //
        // We can get around this mismatch by constructing a specialized reference and
        // then doing the comparison. For example `IBase::doThing<X>` and `Derived::doThing<X>`
        // should both have the signature `X -> void`.
        //
        // The one big detail that we need to be careful about here is that when we
        // recursively call `doesMemberSatisfyRequirement`, that will eventually store
        // the satisfying `DeclRef` as the value for the given requirement key, and we don't
        // want to store a specialized reference like `Derived::doThing<X>` - we need to
        // somehow store the original declaration.
        //
        // The solution here is to specialize the *required* declaration to the parameters
        // of the satisfying declaration. In the example above that means we are going to
        // compare `Derived::doThing` against `IBase::doThing<U>` where the `U` there is
        // the parameter of `Dervived::doThing`.
        //
        GenericSubstitution* requiredSubst = m_astBuilder->create<GenericSubstitution>();
        requiredSubst->genericDecl = requiredGenericDeclRef.getDecl();
        requiredSubst->outer = requiredGenericDeclRef.substitutions;

        for (Index i = 0; i < memberCount; i++)
        {
            auto requiredMemberDeclRef = requiredMemberDeclRefs[i];
            auto satisfyingMemberDeclRef = satisfyingMemberDeclRefs[i];

            if(auto requiredTypeParamDeclRef = requiredMemberDeclRef.as<GenericTypeParamDecl>())
            {
                auto satisfyingTypeParamDeclRef = satisfyingMemberDeclRef.as<GenericTypeParamDecl>();
                SLANG_ASSERT(satisfyingTypeParamDeclRef);
                auto satisfyingType = DeclRefType::create(m_astBuilder, satisfyingTypeParamDeclRef);

                requiredSubst->args.add(satisfyingType);
            }
            else if (auto requiredValueParamDeclRef = requiredMemberDeclRef.as<GenericValueParamDecl>())
            {
                auto satisfyingValueParamDeclRef = satisfyingMemberDeclRef.as<GenericValueParamDecl>();
                SLANG_ASSERT(satisfyingValueParamDeclRef);

                auto satisfyingVal = m_astBuilder->create<GenericParamIntVal>();
                satisfyingVal->declRef = satisfyingValueParamDeclRef;

                requiredSubst->args.add(satisfyingVal);
            }
        }
        for (Index i = 0; i < memberCount; i++)
        {
            auto requiredMemberDeclRef = requiredMemberDeclRefs[i];
            auto satisfyingMemberDeclRef = satisfyingMemberDeclRefs[i];

            if(auto requiredConstraintDeclRef = requiredMemberDeclRef.as<GenericTypeConstraintDecl>())
            {
                auto satisfyingConstraintDeclRef = satisfyingMemberDeclRef.as<GenericTypeConstraintDecl>();
                SLANG_ASSERT(satisfyingConstraintDeclRef);

                auto satisfyingWitness = m_astBuilder->create<DeclaredSubtypeWitness>();
                satisfyingWitness->sub = getSub(m_astBuilder, satisfyingConstraintDeclRef);
                satisfyingWitness->sup = getSup(m_astBuilder, satisfyingConstraintDeclRef);
                satisfyingWitness->declRef = satisfyingConstraintDeclRef;

                requiredSubst->args.add(satisfyingWitness);
            }
        }

        // Now that we have computed a set of specialization arguments that will
        // specialize the generic requirement at the type parameters of the satisfying
        // generic, we can construct a reference to that declaration and re-run some
        // of the earlier checking logic with more type information usable.
        //
        auto specializedRequiredGenericDeclRef = DeclRef<GenericDecl>(requiredGenericDeclRef.getDecl(), requiredSubst);
        auto specializedRequiredMemberDeclRefs = getMembers(specializedRequiredGenericDeclRef);
        for (Index i = 0; i < memberCount; i++)
        {
            auto requiredMemberDeclRef = specializedRequiredMemberDeclRefs[i];
            auto satisfyingMemberDeclRef = satisfyingMemberDeclRefs[i];

            if(auto requiredTypeParamDeclRef = requiredMemberDeclRef.as<GenericTypeParamDecl>())
            {
                auto satisfyingTypeParamDeclRef = satisfyingMemberDeclRef.as<GenericTypeParamDecl>();
                SLANG_ASSERT(satisfyingTypeParamDeclRef);

                // There are no additional checks we need to make on plain old
                // type parameters at this point.
                //
                // TODO: If we ever support having type parameters of higher kinds,
                // then this is possibly where we'd want to check that the kinds of
                // the two parameters match.
                //
                SLANG_UNUSED(satisfyingGenericDeclRef);
            }
            else if (auto requiredValueParamDeclRef = requiredMemberDeclRef.as<GenericValueParamDecl>())
            {
                auto satisfyingValueParamDeclRef = satisfyingMemberDeclRef.as<GenericValueParamDecl>();
                SLANG_ASSERT(satisfyingValueParamDeclRef);

                // For a generic value parameter, we need to check that the required
                // and satisfying declaration both agree on the type of the parameter.
                //
                auto requiredParamType = getType(m_astBuilder, requiredValueParamDeclRef);
                auto satisfyingParamType = getType(m_astBuilder, satisfyingValueParamDeclRef);
                if (!satisfyingParamType->equals(requiredParamType))
                    return false;
            }
            else if(auto requiredConstraintDeclRef = requiredMemberDeclRef.as<GenericTypeConstraintDecl>())
            {
                auto satisfyingConstraintDeclRef = satisfyingMemberDeclRef.as<GenericTypeConstraintDecl>();
                SLANG_ASSERT(satisfyingConstraintDeclRef);

                // For a generic constraint parameter, we need to check that the sub-type
                // and super-type in the constraint both match.
                //
                // In current code the sub type will always be one of the generic type parameters,
                // and the super-type will always be an interface, but there should be no
                // need to make use of those additional details here.

                auto requiredSubType = getSub(m_astBuilder, requiredConstraintDeclRef);
                auto satisfyingSubType = getSub(m_astBuilder, satisfyingConstraintDeclRef);
                if (!satisfyingSubType->equals(requiredSubType))
                    return false;

                auto requiredSuperType = getSup(m_astBuilder, requiredConstraintDeclRef);
                auto satisfyingSuperType = getSup(m_astBuilder, satisfyingConstraintDeclRef);
                if (!satisfyingSuperType->equals(requiredSuperType))
                    return false;
            }
        }

        // Note: the above logic really only applies to the case of an exact match on signature,
        // even down to the way that constraints were declared. We could potentially be more
        // relaxed by taking advantage of the way that various different generic signatures will
        // actually lower to the same IR generic signature.
        //
        // In theory, all we really care about when it comes to constraints is that the constraints
        // on the required and satisfying declaration are *equivalent*.
        //
        // More generally, a satisfying generic could actually provide *looser* constraints and
        // still work; all that matters is that it can be instantiated at any argument values/types
        // that are valid for the requirement.
        //
        // We leave both of those issues up to the synthesis path: if we do not find a member that
        // provides an exact match, then the compiler should try to synthesize one that is an exact
        // match and makes use of existing declarations that might have require defaulting of arguments
        // or type conversations to fit.

        // Once we've validated that the generic signatures are in an exact match, and devised type
        // arguments for the requirement to make the two align, we can recursively check the inner
        // declaration (whatever it is) for an exact match.
        //
        return doesMemberSatisfyRequirement(
            DeclRef<Decl>(satisfyingGenericDeclRef.getDecl()->inner, satisfyingGenericDeclRef.substitutions),
            DeclRef<Decl>(requiredGenericDeclRef.getDecl()->inner, requiredSubst),
            witnessTable);
    }

    bool SemanticsVisitor::doesTypeSatisfyAssociatedTypeRequirement(
        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(m_astBuilder, 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->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 satisfied, then the chosen
            // type can indeed satisfy the interface requirement.
            witnessTable->add(
                requiredAssociatedTypeDeclRef.getDecl(),
                RequirementWitness(satisfyingType));
        }

        return conformance;
    }

    bool SemanticsVisitor::doesMemberSatisfyRequirement(
        DeclRef<Decl>               memberDeclRef,
        DeclRef<Decl>               requiredMemberDeclRef,
        RefPtr<WitnessTable>        witnessTable)
    {
        // Sanity check: if are checking whether a type `T`
        // implements, say, `IFoo::bar` and lookup of `bar`
        // in type `T` yielded `IFoo::bar`, then that shouldn't
        // be treated as a valid satisfaction of the requirement.
        //
        // TODO: Ideally this check should be comparing the `DeclRef`s
        // and not just the `Decl`s, but we currently don't get exactly
        // the same substitutions when we see the inherited `IFoo::bar`.
        //
        if(memberDeclRef.getDecl() == requiredMemberDeclRef.getDecl())
            return false;

        // 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>())
            {
                ensureDecl(subAggTypeDeclRef, DeclCheckState::CanUseAsType);

                auto satisfyingType = DeclRefType::create(m_astBuilder, 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>())
            {
                ensureDecl(typedefDeclRef, DeclCheckState::CanUseAsType);

                auto satisfyingType = getNamedType(m_astBuilder, typedefDeclRef);
                return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable);
            }
        }
        else if( auto propertyDeclRef = memberDeclRef.as<PropertyDecl>() )
        {
            if( auto requiredPropertyDeclRef = requiredMemberDeclRef.as<PropertyDecl>() )
            {
                ensureDecl(propertyDeclRef, DeclCheckState::CanUseFuncSignature);
                return doesPropertyMatchRequirement(propertyDeclRef, requiredPropertyDeclRef, witnessTable);
            }
        }
        // Default: just assume that thing aren't being satisfied.
        return false;
    }

    bool SemanticsVisitor::trySynthesizeMethodRequirementWitness(
        ConformanceCheckingContext* context,
        LookupResult const&         lookupResult,
        DeclRef<FuncDecl>           requiredMemberDeclRef,
        RefPtr<WitnessTable>        witnessTable)
    {
        // The situation here is that the context of an inheritance
        // declaration didn't provide an exact match for a required
        // method. E.g.:
        //
        //      interface ICounter { [mutating] int increment(); }
        //      struct MyCounter : ICounter
        //      {
        //          [murtating] int increment(int val = 1) { ... }
        //      }
        //
        // It is clear in this case that the `MyCounter` type *can*
        // satisfy the signature required by `ICounter`, but it has
        // no explicit method declaration that is a perfect match.
        //
        // The approach in this function will be to construct a
        // synthesized method along the lines of:
        //
        //      struct MyCounter ...
        //      {
        //          ...
        //          [murtating] int synthesized()
        //          {
        //              return this.increment();
        //          }
        //      }
        //
        // That is, we construct a method with the exact signature
        // of the requirement (same parameter and result types),
        // and then provide it with a body that simple `return`s
        // the result of applying the desired requirement name
        // (`increment` in this case) to those parameters.
        //
        // If the synthesized method type-checks, then we can say
        // that the type must satisfy the requirement structurally,
        // even if there isn't an exact signature match. More
        // importantly, the method we just synthesized can be
        // used as a witness to the fact that the requirement is
        // satisfied.

        // With the big picture spelled out, we can settle into
        // the work of constructing our synthesized method.
        //
        auto synFuncDecl = m_astBuilder->create<FuncDecl>();

        // For now our synthesized method will use the name and source
        // location of the requirement we are trying to satisfy.
        //
        // TODO: as it stands right now our syntesized method will
        // get a mangled name, which we don't actually want. Leaving
        // out the name here doesn't help matters, because then *all*
        // snthesized methods on a given type would share the same
        // mangled name!
        //
        synFuncDecl->nameAndLoc = requiredMemberDeclRef.getDecl()->nameAndLoc;

        // The result type of our synthesized method will be the expected
        // result type from the interface requirement.
        //
        // TODO: This logic can/will run into problems if the return type
        // is an associated type.
        //
        // The ideal solution is that we should be solving for interface
        // conformance in two phases: a first phase to solve for how
        // associated types are satisfied, and then a second phase to solve
        // for how other requirements are satisfied (where we can substitute
        // in the associated type witnesses for the abstract associated
        // types as part of `requiredMemberDeclRef`).
        //
        // TODO: We should also double-check that this logic will work
        // with a method that returns `This`.
        //
        auto resultType = getResultType(m_astBuilder, requiredMemberDeclRef);
        synFuncDecl->returnType.type = resultType;

        // Our synthesized method will have parameters matching the names
        // and types of those on the requirement, and it will use expressions
        // that reference those parametesr as arguments for the call expresison
        // that makes up the body.
        //
        List<Expr*> synArgs;
        for( auto paramDeclRef : getParameters(requiredMemberDeclRef) )
        {
            auto paramType = getType(m_astBuilder, paramDeclRef);

            // For each parameter of the requirement, we create a matching
            // parameter (same name and type) for the synthesized method.
            //
            auto synParamDecl = m_astBuilder->create<ParamDecl>();
            synParamDecl->nameAndLoc = paramDeclRef.getDecl()->nameAndLoc;
            synParamDecl->type.type = paramType;

            // We need to add the parameter as a child declaration of
            // the method we are building.
            //
            synParamDecl->parentDecl = synFuncDecl;
            synFuncDecl->members.add(synParamDecl);

            // For each paramter, we will create an argument expression
            // for the call in the function body.
            //
            auto synArg = m_astBuilder->create<VarExpr>();
            synArg->declRef = makeDeclRef(synParamDecl);
            synArg->type = paramType;
            synArgs.add(synArg);
        }

        // Required interface methods can be `static` or non-`static`,
        // and non-`static` methods can be `[mutating]` or non-`[mutating]`.
        // All of these details affect how we introduce our `this` parameter,
        // if any.
        //
        ThisExpr* synThis = nullptr;
        if( requiredMemberDeclRef.getDecl()->hasModifier<HLSLStaticModifier>() )
        {
            auto synStaticModifier = m_astBuilder->create<HLSLStaticModifier>();
            synFuncDecl->modifiers.first = synStaticModifier;
        }
        else
        {
            // For a non-`static` requirement, we need a `this` parameter.
            //
            synThis = m_astBuilder->create<ThisExpr>();

            // The type of `this` in our method will be the type for
            // which we are synthesizing a conformance.
            //
            synThis->type.type = context->conformingType;

            if( requiredMemberDeclRef.getDecl()->hasModifier<MutatingAttribute>() )
            {
                // If the interface requirement is `[mutating]` then our
                // synthesized method should be too, and also the `this`
                // parameter should be an l-value.
                //
                synThis->type.isLeftValue = true;

                auto synMutatingAttr = m_astBuilder->create<MutatingAttribute>();
                synFuncDecl->modifiers.first = synMutatingAttr;
            }
        }

        // The body of our synthesized method is going to try to
        // make a call using the name of the method requirement (e.g.,
        // the name `increment` in our example at the top of this function).
        //
        // The caller already passed in a `LookupResult` that represents
        // an attempt to look up the given name in the type of `this`,
        // and we really just need to wrap that result up as an overloaded
        // expression.
        //
        auto synBase = m_astBuilder->create<OverloadedExpr>();
        synBase->name = requiredMemberDeclRef.getDecl()->getName();
        synBase->lookupResult2 = lookupResult;

        // If `synThis` is non-null, then we will use it as the base of
        // the overloaded expression, so that we have an overloaded
        // member reference, and not just an overloaded reference to some
        // static definitions.
        //
        synBase->base = synThis;

        // We now have the reference to the overload group we plan to call,
        // and we already built up the argument list, so we can construct
        // an `InvokeExpr` that represents the call we want to make.
        //
        auto synCall = m_astBuilder->create<InvokeExpr>();
        synCall->functionExpr = synBase;
        synCall->arguments = synArgs;

        // In order to know if our call is well-formed, we need to run
        // the semantic checking logic for overload resolution. If it
        // runs into an error, we don't want that being reported back
        // to the user as some kind of overload-resolution failure.
        //
        // In order to protect the user from whatever errors might
        // occur, we will swap out the current diagnostic sink for
        // a temporary one.
        //
        DiagnosticSink* savedSink = m_shared->m_sink;
        DiagnosticSink tempSink(savedSink->getSourceManager(), nullptr);
        m_shared->m_sink = &tempSink;

        // With our temporary diagnostic sink soaking up any messages
        // from overload resolution, we can now try to resolve
        // the call to see what happens.
        //
        auto checkedCall = ResolveInvoke(synCall);

        // Of course, it is possible that the call went through fine,
        // but the result isn't of the type we expect/require,
        // so we also need to coerce the result of the call to
        // the expected type.
        //
        auto coercedCall = coerce(resultType, checkedCall);

        // Once we are done making our semantic checks, we can
        // restore the original sink, so that subsequent operations
        // report diagnostics as usual.
        //
        m_shared->m_sink = savedSink;

        // If our overload resolution or type coercion failed,
        // then we have not been able to synthesize a witness
        // for the requirement.
        //
        // TODO: We might want to detect *why* overload resolution
        // or type coercion failed, and report errors accordingly.
        //
        // More detailed diagnostics could help users understand
        // what they did wrong, e.g.:
        //
        // * "We tried to use `foo(int)` but the interface requires `foo(String)`
        //
        // * "You have two methods that can apply as `bar()` and we couldn't tell which one you meant
        //
        // For now we just bail out here and rely on the caller to
        // diagnose a generic "failed to satisfying requirement" error.
        //
        if(tempSink.getErrorCount() != 0)
            return false;

        // If we were able to type-check the call, then we should
        // be able to finish construction of a suitable witness.
        //
        // We've already created the outer declaration (including its
        // parameters), and the inner expression, so the main work
        // that is left is defining the body of the new function,
        // which comprises a single `return` statement.
        //
        auto synReturn = m_astBuilder->create<ReturnStmt>();
        synReturn->expression = coercedCall;

        synFuncDecl->body = synReturn;

        // Once we are sure that we want to use the declaration
        // we've synthesized, aew can go ahead and wire it up
        // to the AST so that subsequent stages can generate
        // IR code from it.
        //
        // Note: we set the parent of the synthesized declaration
        // to the parent of the inheritance declaration being
        // validated (which is either a type declaration or
        // an `extension`), but we do *not* add the syntehsized
        // declaration to the list of child declarations at
        // this point.
        //
        // By leaving the synthesized declaration off of the list
        // of members, we ensure that it doesn't get found
        // by lookup (e.g., in a module that `import`s this type).
        // Unfortunately, we may also break invariants in other parts
        // of the code if they assume that all declarations have
        // to appear in the parent/child hierarchy of the module.
        //
        // TODO: We may need to properly wire the synthesized
        // declaration into the hierarchy, but then attach a modifier
        // to it to indicate that it should be ignored by things like lookup.
        //
        synFuncDecl->parentDecl = context->parentDecl;

        // Once our synthesized declaration is complete, we need
        // to install it as the witness that satifies the given
        // requirement.
        //
        // Subsequent code generation should not be able to tell the
        // difference between our synthetic method and a hand-written
        // one with the same behavior.
        //
        witnessTable->add(requiredMemberDeclRef,
            RequirementWitness(makeDeclRef(synFuncDecl)));
        return true;
    }

    bool SemanticsVisitor::trySynthesizePropertyRequirementWitness(
        ConformanceCheckingContext* context,
        LookupResult const&         lookupResult,
        DeclRef<PropertyDecl>       requiredMemberDeclRef,
        RefPtr<WitnessTable>        witnessTable)
    {
        // The situation here is that the context of an inheritance
        // declaration didn't provide an exact match for a required
        // property. E.g.:
        //
        //      interface ICell { property value : int { get; set; } }
        //      struct MyCell : ICell
        //      {
        //          int value;
        //      }
        //
        // It is clear in this case that the `MyCell` type *can*
        // satisfy the signature required by `ICell`, but it has
        // no explicit `property` declaration, and instead just
        // a field with the right name and type.
        //
        // The approach in this function will be to construct a
        // synthesized `preoperty` along the lines of:
        //
        //      struct MyCounter ...
        //      {
        //          ...
        //          property value_synthesized : int
        //          {
        //              get { return this.value; }
        //              set(newValue) { this.value = newValue; }
        //          }
        //      }
        //
        // That is, we construct a `property` with the correct type
        // and with an accessor for each requirement, where the accesors
        // all try to read or write `this.value`.
        //
        // If those synthesized accessors all type-check, then we can
        // say that the type must satisfy the requirement structurally,
        // even if there isn't an exact signature match. More
        // importantly, the `property` we just synthesized can be
        // used as a witness to the fact that the requirement is
        // satisfied.
        //
        // The big-picture flow of the logic here is similar to
        // `trySynthesizeMethodRequirementWitness()` above, and we
        // will not comment this code as exhaustively, under the
        // assumption that readers of the code don't benefit from
        // having the exact same information stated twice.

        // With the introduction out of the way, let's get started
        // constructing a synthesized `PropertyDecl`.
        //
        auto synPropertyDecl = m_astBuilder->create<PropertyDecl>();

        // For now our synthesized property will use the name and source
        // location of the requirement we are trying to satisfy.
        //
        // TODO: as it stands right now our syntesized property and its
        // accesors will get mangled names, which we don't actually want.
        // Leaving out the name here doesn't help matters, becaues then
        // *all* synthesized members on a given type would share the same
        // mangled name.
        //
        synPropertyDecl->nameAndLoc = requiredMemberDeclRef.getDecl()->nameAndLoc;

        // The type of our synthesized property will be the expected type
        // of the interface requirement.
        //
        // TODO: This logic can/will run into problems if the type is,
        // or uses, an associated type or `This`.
        //
        // Ideally we should be looking up the type using a `DeclRef` that
        // refers to the interface requirement using a `ThisTypeSubstitution`
        // that refers to the satisfying type declaration, and requirement
        // checking for non-associated-type requirements should be done *after*
        // requirement checking for associated-type requirements.
        //
        auto propertyType = getType(m_astBuilder, requiredMemberDeclRef);
        synPropertyDecl->type.type = propertyType;

        // Our synthesized property will have an accessor declaration for
        // each accessor of the requirement.
        //
        // TODO: If we ever start to support synthesis for subscript requirements,
        // then we probably want to factor the accessor-related logic into
        // a subroutine so that it can be shared between properties and subscripts.
        //
        Dictionary<DeclRef<AccessorDecl>, AccessorDecl*> mapRequiredAccessorToSynAccessor;
        for( auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(requiredMemberDeclRef) )
        {
            // The synthesized accessor will be an AST node of the same class as
            // the required accessor.
            //
            auto synAccessorDecl = (AccessorDecl*) m_astBuilder->createByNodeType(requiredAccessorDeclRef.getDecl()->astNodeType);

            // Whatever the required accessor returns, that is what our synthesized accessor will return.
            //
            synAccessorDecl->returnType.type = getResultType(m_astBuilder, requiredAccessorDeclRef);

            // Similarly, our synthesized accessor will have parameters matching those of the requirement.
            //
            // Note: in practice we expect that only `set` accessors will have any parameters,
            // and they will only have a single parameter.
            //
            List<Expr*> synArgs;
            for( auto requiredParamDeclRef : getParameters(requiredAccessorDeclRef) )
            {
                auto paramType = getType(m_astBuilder, requiredParamDeclRef);

                // The synthesized parameter will ahve the same name and
                // type as the parameter of the requirement.
                //
                auto synParamDecl = m_astBuilder->create<ParamDecl>();
                synParamDecl->nameAndLoc = requiredParamDeclRef.getDecl()->nameAndLoc;
                synParamDecl->type.type = paramType;

                // We need to add the parameter as a child declaration of
                // the accessor we are building.
                //
                synParamDecl->parentDecl = synAccessorDecl;
                synAccessorDecl->members.add(synParamDecl);

                // For each paramter, we will create an argument expression
                // to represent it in the body of the accessor.
                //
                auto synArg = m_astBuilder->create<VarExpr>();
                synArg->declRef = makeDeclRef(synParamDecl);
                synArg->type = paramType;
                synArgs.add(synArg);
            }

            // We need to create a `this` expression to be used in the body
            // of the synthesized accessor.
            //
            // TODO: if we ever allow `static` properties or subscripts,
            // we will need to handle that case here, by *not* creating
            // a `this` expression.
            //
            ThisExpr* synThis = m_astBuilder->create<ThisExpr>();

            // The type of `this` in our accessor will be the type for
            // which we are synthesizing a conformance.
            //
            synThis->type.type = context->conformingType;

            // A `get` accessor should default to an immutable `this`,
            // while other accessors default to mutable `this`.
            //
            // TODO: If we ever add other kinds of accessors, we will
            // need to check that this assumption stays valid.
            //
            synThis->type.isLeftValue = true;
            if(as<GetterDecl>(requiredAccessorDeclRef))
                synThis->type.isLeftValue = false;

            // If the accessor requirement is `[nonmutating]` then our
            // synthesized accessor should be too, and also the `this`
            // parameter should *not* be an l-value.
            //
            if( requiredAccessorDeclRef.getDecl()->hasModifier<NonmutatingAttribute>() )
            {
                synThis->type.isLeftValue = false;

                auto synAttr = m_astBuilder->create<NonmutatingAttribute>();
                synAccessorDecl->modifiers.first = synAttr;
            }
            //
            // Note: we don't currently support `[mutating] get` accessors,
            // but the desired behavior in that case is clear, so we go
            // ahead and future-proof this code a bit:
            //
            else if( requiredAccessorDeclRef.getDecl()->hasModifier<MutatingAttribute>() )
            {
                synThis->type.isLeftValue = true;

                auto synAttr = m_astBuilder->create<MutatingAttribute>();
                synAccessorDecl->modifiers.first = synAttr;
            }

            // We are going to synthesize an expression and then perform
            // semantic checking on it, but if there are semantic errors
            // we do *not* want to report them to the user as such, and
            // instead want the result to be a failure to synthesize
            // a valid witness.
            //
            // We will buffer up diagnostics into a temporary sink and
            // then throw them away when we are done.
            //
            // TODO: This behavior might be something we want to make
            // into a more fundamental capability of `DiagnosticSink` and/or
            // `SemanticsVisitor` so that code can push/pop the emission
            // of diagnostics more easily.
            //
            DiagnosticSink* savedSink = m_shared->m_sink;
            DiagnosticSink tempSink(savedSink->getSourceManager(), nullptr);
            m_shared->m_sink = &tempSink;

            // We start by constructing an expression that represents
            // `this.name` where `name` is the name of the required
            // member. The caller already passed in a `lookupResult`
            // that should indicate all the declarations found by
            // looking up `name`, so we can start with that.
            //
            // TODO: Note that there are many cases for member lookup
            // that are not handled just by using `createLookupResultExpr`
            // because they are currently being special-cased (the most
            // notable cases are swizzles, as well as lookup of static
            // members in types).
            //
            // The main result here is that we will not be able to synthesize
            // a requirement for a built-in scalar/vector/matrix type to
            // a property with a name like `.xy` based on the presence of
            // swizles, even though it seems like such a thing should Just Work.
            //
            // If this is important we could "fix" it by allowing this
            // code to dispatch to the special-case logic used when doing
            // semantic checking for member expressions.
            //
            // Note: an alternative would be to change the stdlib declarations
            // of vectors/matrices so that all the swizzles are defined as
            // `property` declarations. There are some C++ math libraries (like GLM)
            // that implement swizzle syntax by a similar approach of statically
            // enumerating all possible swizzles. The down-side to such an
            // approach is that the combinatorial space of swizzles is quite
            // large (especially for matrices) so that supporting them via
            // general-purpose language features is unlikely to be as efficient
            // as special-case logic.
            //
            auto synMemberRef = createLookupResultExpr(
                requiredMemberDeclRef.getName(),
                lookupResult,
                synThis,
                requiredMemberDeclRef.getLoc());

            // The body of the accessor will depend on the class of the accessor
            // we are synthesizing (e.g., `get` vs. `set`).
            //
            Stmt* synBodyStmt = nullptr;
            if( as<GetterDecl>(requiredAccessorDeclRef) )
            {
                // A `get` accessor will simply perform:
                //
                //      return this.name;
                //
                // which involves coercing the member access `this.name` to
                // the expected type of the property.
                //
                auto coercedMemberRef = coerce(propertyType, synMemberRef);
                auto synReturn = m_astBuilder->create<ReturnStmt>();
                synReturn->expression = coercedMemberRef;

                synBodyStmt = synReturn;
            }
            else if( as<SetterDecl>(requiredAccessorDeclRef) )
            {
                // We expect all `set` accessors to have a single argument,
                // but we will defensively bail out if that is somehow
                // not the case.
                //
                SLANG_ASSERT(synArgs.getCount() == 1);
                if(synArgs.getCount() != 1)
                    return false;

                // A `set` accessor will simply perform:
                //
                //      this.name = newValue;
                //
                // which involves creating and checking an assignment
                // expression.

                auto synAssign = m_astBuilder->create<AssignExpr>();
                synAssign->left = synMemberRef;
                synAssign->right = synArgs[0];

                auto synCheckedAssign = checkAssignWithCheckedOperands(synAssign);

                auto synExprStmt = m_astBuilder->create<ExpressionStmt>();
                synExprStmt->expression = synCheckedAssign;

                synBodyStmt = synExprStmt;
            }
            else
            {
                // While there are other kinds of accessors than `get` and `set`,
                // those are currently only reserved for stdlib-internal use.
                // We will not bother with synthesis for those cases.
                //
                return false;
            }

            // We restore the semantic checking state that was in place before
            // we checked the synthesized accessor body, and then bail out
            // if we ran into any errors (meaning that the synthesized accessor
            // is not usable).
            //
            // TODO: If there were *warnings* emitted to the sink, it would probably
            // be good to show those warnings to the user, since they might indicate
            // real issues. E.g., with the current logic a `float` field could
            // satisfying an `int` property requirement, but the user would probably
            // want to be warned when they do such a thing.
            //
            m_shared->m_sink = savedSink;
            if(tempSink.getErrorCount() != 0)
                return false;

            synAccessorDecl->body = synBodyStmt;

            synAccessorDecl->parentDecl = synPropertyDecl;
            synPropertyDecl->members.add(synAccessorDecl);

            // If synthesis of an accessor worked, then we will record it into
            // a local dictionary. We do *not* install the accessor into the
            // witness table yet, because it is possible that synthesis will
            // succeed for some accessors but not others, and we don't want
            // to leave the witness table in a state where a requirement is
            // "partially satisfied."
            //
            mapRequiredAccessorToSynAccessor.Add(requiredAccessorDeclRef, synAccessorDecl);
        }

        synPropertyDecl->parentDecl = context->parentDecl;

        // Once our synthesized declaration is complete, we need
        // to install it as the witness that satifies the given
        // requirement.
        //
        // Subsequent code generation should not be able to tell the
        // difference between our synthetic property and a hand-written
        // one with the same behavior.
        //
        for(auto p : mapRequiredAccessorToSynAccessor)
        {
            witnessTable->add(p.Key, RequirementWitness(makeDeclRef(p.Value)));
        }
        witnessTable->add(requiredMemberDeclRef,
            RequirementWitness(makeDeclRef(synPropertyDecl)));
        return true;
    }


    bool SemanticsVisitor::trySynthesizeRequirementWitness(
        ConformanceCheckingContext* context,
        LookupResult const&         lookupResult,
        DeclRef<Decl>               requiredMemberDeclRef,
        RefPtr<WitnessTable>        witnessTable)
    {
        SLANG_UNUSED(lookupResult);
        SLANG_UNUSED(requiredMemberDeclRef);
        SLANG_UNUSED(witnessTable);

        if (auto requiredFuncDeclRef = requiredMemberDeclRef.as<FuncDecl>())
        {
            // Check signature match.
            return trySynthesizeMethodRequirementWitness(
                context,
                lookupResult,
                requiredFuncDeclRef,
                witnessTable);
        }

        if( auto requiredPropertyDeclRef = requiredMemberDeclRef.as<PropertyDecl>() )
        {
            return trySynthesizePropertyRequirementWitness(
                context,
                lookupResult,
                requiredPropertyDeclRef,
                witnessTable);
        }

        // TODO: There are other kinds of requirements for which synthesis should
        // be possible:
        //
        // * It should be possible to synthesize required initializers
        //   using an approach similar to what is used for methods.
        //
        // * We should be able to synthesize subscripts with different
        //   signatures (taking into account default parameters).
        //
        // * For specific kinds of generic requirements, we should be able
        //   to wrap the synthesis of the inner declaration in synthesis
        //   of an outer generic with a matching signature.
        //
        // All of these cases can/should use similar logic to
        // `trySynthesizeMethodRequirementWitness` where they construct an AST
        // in the form of what the use site ought to look like, and then
        // apply existing semantic checking logic to generate the code.

        return false;
    }

    bool SemanticsVisitor::findWitnessForInterfaceRequirement(
        ConformanceCheckingContext* context,
        Type*                       subType,
        Type*                       superInterfaceType,
        InheritanceDecl*            inheritanceDecl,
        DeclRef<InterfaceDecl>      superInterfaceDeclRef,
        DeclRef<Decl>               requiredMemberDeclRef,
        RefPtr<WitnessTable>        witnessTable,
        SubtypeWitness*             subTypeConformsToSuperInterfaceWitness)
    {
        SLANG_UNUSED(superInterfaceDeclRef)

        // 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!!!!

            auto reqType = getBaseType(m_astBuilder, requiredInheritanceDeclRef);

            DeclaredSubtypeWitness* interfaceIsReqWitness = m_astBuilder->create<DeclaredSubtypeWitness>();
            interfaceIsReqWitness->sub = superInterfaceType;
            interfaceIsReqWitness->sup = reqType;
            interfaceIsReqWitness->declRef = requiredInheritanceDeclRef;
            // ...

            TransitiveSubtypeWitness* subIsReqWitness = m_astBuilder->create<TransitiveSubtypeWitness>();
            subIsReqWitness->sub = subType;
            subIsReqWitness->sup = reqType;
            subIsReqWitness->subToMid = subTypeConformsToSuperInterfaceWitness;
            subIsReqWitness->midToSup = interfaceIsReqWitness;
            // ...

            RefPtr<WitnessTable> satisfyingWitnessTable = new WitnessTable();
            satisfyingWitnessTable->witnessedType = subType;
            satisfyingWitnessTable->baseType = reqType;

            witnessTable->add(
                requiredInheritanceDeclRef.getDecl(),
                RequirementWitness(satisfyingWitnessTable));

            if( !checkConformanceToType(
                context,
                subType,
                requiredInheritanceDeclRef.getDecl(),
                reqType,
                subIsReqWitness,
                satisfyingWitnessTable) )
            {
                return false;
            }

            return true;
        }

        // We will look up members with the same name,
        // since only same-name members will be able to
        // satisfy the requirement.
        //
        Name* name = requiredMemberDeclRef.getName();

        // We start by looking up members of the same
        // name, on the type that is claiming to conform.
        //
        // This lookup step could include members that
        // we might not actually want to consider:
        //
        // * Lookup through a type `Foo` where `Foo : IBar`
        //   will be able to find members of `IBar`, which
        //   somewhat obviously shouldn't apply when
        //   determining if `Foo` satisfies the requirements
        //   of `IBar`.
        //
        // * Lookup in the presence of `__transparent` members
        //   may produce references to declarations on a *field*
        //   of the type rather than the type. Conformance through
        //   transparent members could be supported in theory,
        //   but would require synthesizing proxy/forwarding
        //   implementations in the type itself.
        //
        // For the first issue, we will use a flag to influence
        // lookup so that it doesn't include results looked up
        // through interface inheritance clauses (but it *will*
        // look up result through inheritance clauses corresponding
        // to concrete types).
        //
        // The second issue of members that require us to proxy/forward
        // requests will be handled further down. For now we include
        // lookup results that might be usable, but not as-is.
        //
        auto lookupResult = lookUpMember(m_astBuilder, this, name, subType, LookupMask::Default, LookupOptions::IgnoreBaseInterfaces);

        if(!lookupResult.isValid())
        {
            // If we failed to even look up a member with the name of the
            // requirement, then we can be certain that the type doesn't
            // satisfy the requirement.
            //
            // TODO: If we ever allowed certain kinds of requirements to
            // be inferred (e.g., inferring associated types from the
            // signatures of methods, as is done for Swift), we'd
            // need to revisit this step.
            //
            getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, subType, requiredMemberDeclRef);
            getSink()->diagnose(requiredMemberDeclRef, Diagnostics::seeDeclarationOf, requiredMemberDeclRef);
            return false;
        }

        // Iterate over the members and look for one that matches
        // the expected signature for the requirement.
        for (auto member : lookupResult)
        {
            // To a first approximation, any lookup result that required a "breadcrumb"
            // will not be usable to directly satisfy an interface requirement, since
            // each breadcrumb will amount to a manipulation of `this` that is required
            // to make the declaration usable (e.g., casting to a base type).
            //
            if(member.breadcrumbs != nullptr)
                continue;

            if (doesMemberSatisfyRequirement(member.declRef, requiredMemberDeclRef, witnessTable))
                return true;
        }

        // If we reach this point then there were no members suitable
        // for satisfying the interface requirement *diretly*.
        //
        // It is possible that one of the items in `lookupResult` could be
        // used to synthesize an exact-match witness, by generating the
        // code required to handle all the conversions that might be
        // required on `this`.
        //
        if( trySynthesizeRequirementWitness(context, lookupResult, requiredMemberDeclRef, witnessTable) )
        {
            return true;
        }

        // We failed to find a member of the type that can be used
        // to satisfy the requirement (even via synthesis), so we
        // need to report the failure to the user.
        //
        // 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 that made it
        // furthest in checking.
        //
        getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, subType, requiredMemberDeclRef);
        getSink()->diagnose(requiredMemberDeclRef, Diagnostics::seeDeclarationOf, requiredMemberDeclRef);
        return false;
    }

    RefPtr<WitnessTable> SemanticsVisitor::checkInterfaceConformance(
        ConformanceCheckingContext* context,
        Type*                       subType,
        Type*                       superInterfaceType,
        InheritanceDecl*            inheritanceDecl,
        DeclRef<InterfaceDecl>      superInterfaceDeclRef,
        SubtypeWitness*             subTypeConformsToSuperInterfaceWitnes)
    {
        // Has somebody already checked this conformance,
        // and/or is in the middle of checking it?
        RefPtr<WitnessTable> witnessTable;
        if(context->mapInterfaceToWitnessTable.TryGetValue(superInterfaceDeclRef, witnessTable))
            return witnessTable;

        // We need to check the declaration of the interface
        // before we can check that we conform to it.
        //
        ensureDecl(superInterfaceDeclRef, DeclCheckState::CanReadInterfaceRequirements);

        // 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();
            witnessTable->baseType = DeclRefType::create(m_astBuilder, superInterfaceDeclRef);
            witnessTable->witnessedType = subType;
        }
        context->mapInterfaceToWitnessTable.Add(superInterfaceDeclRef, witnessTable);

        if(!checkInterfaceConformance(context, subType, superInterfaceType, inheritanceDecl, superInterfaceDeclRef, subTypeConformsToSuperInterfaceWitnes, witnessTable))
            return nullptr;

        return witnessTable;
    }

    static bool isAssociatedTypeDecl(Decl* decl)
    {
        auto d = decl;
        while(auto genericDecl = as<GenericDecl>(d))
            d = genericDecl->inner;
        if(as<AssocTypeDecl>(d))
            return true;
        return false;
    }

    bool SemanticsVisitor::checkInterfaceConformance(
        ConformanceCheckingContext* context,
        Type*                       subType,
        Type*                       superInterfaceType,
        InheritanceDecl*            inheritanceDecl,
        DeclRef<InterfaceDecl>      superInterfaceDeclRef,
        SubtypeWitness*             subTypeConformsToSuperInterfaceWitness,
        WitnessTable*               witnessTable)
    {
        // We need to check the declaration of the interface
        // before we can check that we conform to it.
        //
        ensureDecl(superInterfaceDeclRef, DeclCheckState::CanReadInterfaceRequirements);

        // When comparing things like signatures, we need to do so in the context
        // of a this-type substitution that aligns the signatures in the interface
        // with those in the concrete type. For example, we need to treat any uses
        // of `This` in the interface as equivalent to the concrete type for the
        // purpose of signature matching (and similarly for associated types).
        //
        ThisTypeSubstitution* thisTypeSubst = m_astBuilder->create<ThisTypeSubstitution>();
        thisTypeSubst->interfaceDecl = superInterfaceDeclRef.getDecl();
        thisTypeSubst->witness = subTypeConformsToSuperInterfaceWitness;
        thisTypeSubst->outer = superInterfaceDeclRef.substitutions.substitutions;

        auto specializedSuperInterfaceDeclRef = DeclRef<InterfaceDecl>(superInterfaceDeclRef.getDecl(), thisTypeSubst);

        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...

        // Note: we break this logic into two loops, where we first
        // check conformance for all associated-type requirements
        // and *then* check conformance for all other requirements.
        //
        // Checking associated-type requirements first ensures that
        // we can make use of the identity of the associated types
        // when checking other members.
        //
        // TODO: There could in theory be subtle cases involving
        // circular or recursive dependency chains that make such
        // a simple ordering impractical (e.g., associated type `A`
        // is constrained to `IThing<This>` where `IThing<T>` requires
        // that `T : IOtherThing where T.B == int` for another associated
        // type `B`).
        //
        // The only robust solution long-term is probably to treat this
        // as a type-inference problem by creating type variables to
        // stand in for the associated-type requirements and then to discover
        // constraints and solve for those type variables as part of the
        // conformance-checking process.
        //
        for(auto requiredMemberDeclRef : getMembers(specializedSuperInterfaceDeclRef))
        {
            if(!isAssociatedTypeDecl(requiredMemberDeclRef))
                continue;

            auto requirementSatisfied = findWitnessForInterfaceRequirement(
                context,
                subType,
                superInterfaceType,
                inheritanceDecl,
                specializedSuperInterfaceDeclRef,
                requiredMemberDeclRef,
                witnessTable,
                subTypeConformsToSuperInterfaceWitness);

            result = result && requirementSatisfied;
        }
        for(auto requiredMemberDeclRef : getMembers(specializedSuperInterfaceDeclRef))
        {
            if(isAssociatedTypeDecl(requiredMemberDeclRef))
                continue;

            auto requirementSatisfied = findWitnessForInterfaceRequirement(
                context,
                subType,
                superInterfaceType,
                inheritanceDecl,
                specializedSuperInterfaceDeclRef,
                requiredMemberDeclRef,
                witnessTable,
                subTypeConformsToSuperInterfaceWitness);

            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 a bit longer.
        //
        for(auto candidateExt : getCandidateExtensions(specializedSuperInterfaceDeclRef, this))
        {
            // We need to apply the extension to the interface type that our
            // concrete type is inheriting from.
            //
            Type* targetType = DeclRefType::create(m_astBuilder, specializedSuperInterfaceDeclRef);
            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,
                    subType,
                    superInterfaceType,
                    inheritanceDecl,
                    specializedSuperInterfaceDeclRef,
                    requiredInheritanceDeclRef,
                    witnessTable,
                    subTypeConformsToSuperInterfaceWitness);

                result = result && requirementSatisfied;
            }
        }

        // The conformance was satisfied if all the requirements were satisfied.
        //
        return result;
    }

    bool SemanticsVisitor::checkConformanceToType(
        ConformanceCheckingContext* context,
        Type*                       subType,
        InheritanceDecl*            inheritanceDecl,
        Type*                       superType,
        SubtypeWitness*             subIsSuperWitness,
        WitnessTable*               witnessTable)
    {
        if (auto supereclRefType = as<DeclRefType>(superType))
        {
            auto superTypeDeclRef = supereclRefType->declRef;
            if (auto superInterfaceDeclRef = superTypeDeclRef.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,
                    subType,
                    superType,
                    inheritanceDecl,
                    superInterfaceDeclRef,
                    subIsSuperWitness,
                    witnessTable);
            }
            else if( auto superStructDeclRef = superTypeDeclRef.as<StructDecl>() )
            {
                // The type is saying it inherits from a `struct`,
                // which doesn't require any checking at present
                return true;
            }
        }

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

    bool SemanticsVisitor::checkConformance(
        Type*                       subType,
        InheritanceDecl*            inheritanceDecl,
        ContainerDecl*              parentDecl)
    {
        if( auto declRefType = as<DeclRefType>(subType) )
        {
            auto declRef = declRefType->declRef;

            // 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 superType = inheritanceDecl->base.type;

        DeclaredSubtypeWitness* subIsSuperWitness = m_astBuilder->create<DeclaredSubtypeWitness>();
        subIsSuperWitness->declRef = makeDeclRef(inheritanceDecl);
        subIsSuperWitness->sub = subType;
        subIsSuperWitness->sup = superType;

        ConformanceCheckingContext context;
        context.conformingType = subType;
        context.parentDecl = parentDecl;


        RefPtr<WitnessTable> witnessTable = inheritanceDecl->witnessTable;
        if(!witnessTable)
        {
            witnessTable = new WitnessTable();
            witnessTable->baseType = superType;
            witnessTable->witnessedType = subType;
            inheritanceDecl->witnessTable = witnessTable;
        }

        if( !checkConformanceToType(&context, subType, inheritanceDecl, superType, subIsSuperWitness, witnessTable) )
        {
            return false;
        }

        return true;
    }

    void SemanticsVisitor::checkExtensionConformance(ExtensionDecl* decl)
    {
        auto declRef = createDefaultSubstitutionsIfNeeded(m_astBuilder, makeDeclRef(decl)).as<ExtensionDecl>();
        auto targetType = getTargetType(m_astBuilder, declRef);

        for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
        {
            checkConformance(targetType, inheritanceDecl, decl);
        }
    }

    void SemanticsVisitor::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 = as<InterfaceDecl>(decl))
        {
            // Don't check that an interface conforms to the
            // things it inherits from.
        }
        else if (auto assocTypeDecl = as<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.
            //

            auto astBuilder = getASTBuilder();

            auto declRef = createDefaultSubstitutionsIfNeeded(astBuilder, makeDeclRef(decl)).as<AggTypeDeclBase>();
            auto type = DeclRefType::create(astBuilder, declRef);

            // 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(type, inheritanceDecl, decl);
            }
        }
    }

    void SemanticsDeclBasesVisitor::_validateCrossModuleInheritance(
        AggTypeDeclBase* decl,
        InheritanceDecl* inheritanceDecl)
    {
        // Within a single module, users should be allowed to inherit
        // one type from another more or less freely, so long as they
        // don't violate fundamental validity conditions around
        // inheritance.
        //
        // When an inheritance relationship is declared in one module,
        // and the base type is in another module, we may want to
        // enforce more restrictions. As a strong example, we probably
        // don't want people to declare their own subtype of `int`
        // or `Texture2D<float4>`.
        //
        // We start by checking if the type being inherited from is
        // a decl-ref type, since that means it refers to a declaration
        // that can be localized to its original module.
        //
        auto baseType = inheritanceDecl->base.type;
        auto baseDeclRefType = as<DeclRefType>(baseType);
        if( !baseDeclRefType )
        {
            return;
        }
        auto baseDecl = baseDeclRefType->declRef.decl;

        // Using the parent/child hierarchy baked into `Decl`s we
        // can find the modules that contain both the `decl` doing
        // the inheriting, and the `baseDeclRefType` that is being
        // inherited from.
        //
        // If those modules are the same, then we aren't seeing any
        // kind of cross-module inheritance here, and there is nothing
        // that needs enforcing.
        //
        auto moduleWithInheritance = getModule(decl);
        auto moduleWithBaseType = getModule(baseDecl);
        if( moduleWithInheritance == moduleWithBaseType )
        {
            return;
        }

        if( baseDecl->hasModifier<SealedAttribute>() )
        {
            // If the original declaration had the `[sealed]` attribute on it,
            // then it explicitly does *not* allow inheritance from other
            // modules.
            //
            getSink()->diagnose(inheritanceDecl, Diagnostics::cannotInheritFromExplicitlySealedDeclarationInAnotherModule, baseType, moduleWithBaseType->getModuleDecl()->getName());
            return;
        }
        else if( baseDecl->hasModifier<OpenAttribute>() )
        {
            // Conversely, if the original declaration had the `[open]` attribute
            // on it, then it explicit *does* allow inheritance from other
            // modules.
            //
            // In this case we don't need to check anything: the inheritance
            // is allowed.
        }
        else if( as<InterfaceDecl>(baseDecl) )
        {
            // If an interface isn't explicitly marked `[open]` or `[sealed]`,
            // then the default behavior is to treat it as `[open]`, since
            // interfaces are most often used to define protocols that
            // users of a module can opt into.
        }
        else
        {
            // For any non-interface type, if the declaration didn't specify
            // `[open]` or `[sealed]` then we assume `[sealed]` is the default.
            //
            getSink()->diagnose(inheritanceDecl, Diagnostics::cannotInheritFromImplicitlySealedDeclarationInAnotherModule, baseType, moduleWithBaseType->getModuleDecl()->getName());
            return;
        }
    }

    void SemanticsDeclBasesVisitor::visitInterfaceDecl(InterfaceDecl* decl)
    {
        for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
        {
            ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
            auto baseType = inheritanceDecl->base.type;

            // It is possible that there was an error in checking the base type
            // expression, and in such a case we shouldn't emit a cascading error.
            //
            if( auto baseErrorType = as<ErrorType>(baseType) )
            {
                continue;
            }

            // An `interface` type can only inherit from other `interface` types.
            //
            // TODO: In the long run it might make sense for an interface to support
            // an inheritance clause naming a non-interface type, with the meaning
            // that any type that implements the interface must be a sub-type of the
            // type named in the inheritance clause.
            //
            auto baseDeclRefType = as<DeclRefType>(baseType);
            if( !baseDeclRefType )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfInterfaceMustBeInterface, decl, baseType);
                continue;
            }

            auto baseDeclRef = baseDeclRefType->declRef;
            auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>();
            if( !baseInterfaceDeclRef )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfInterfaceMustBeInterface, decl, baseType);
                continue;
            }

            // TODO: At this point we have the `baseInterfaceDeclRef`
            // and could use it to perform further validity checks,
            // and/or to build up a more refined representation of
            // the inheritance graph for this type (e.g., a "class
            // precedence list").
            //
            // E.g., we can/should check that we aren't introducing
            // a circular inheritance relationship.

            _validateCrossModuleInheritance(decl, inheritanceDecl);
        }
    }

    void SemanticsDeclBasesVisitor::visitStructDecl(StructDecl* decl)
    {
        // A `struct` type can only inherit from `struct` or `interface` types.
        //
        // Furthermore, only the first inheritance clause (in source
        // order) is allowed to declare a base `struct` type.
        //
        Index inheritanceClauseCounter = 0;
        for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
        {
            Index inheritanceClauseIndex = inheritanceClauseCounter++;

            ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
            auto baseType = inheritanceDecl->base.type;

            // It is possible that there was an error in checking the base type
            // expression, and in such a case we shouldn't emit a cascading error.
            //
            if( auto baseErrorType = as<ErrorType>(baseType) )
            {
                continue;
            }

            auto baseDeclRefType = as<DeclRefType>(baseType);
            if( !baseDeclRefType )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfStructMustBeStructOrInterface, decl, baseType);
                continue;
            }

            auto baseDeclRef = baseDeclRefType->declRef;
            if( auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>() )
            {
            }
            else if( auto baseStructDeclRef = baseDeclRef.as<StructDecl>() )
            {
                // To simplify the task of reading and maintaining code,
                // we require that when a `struct` inherits from another
                // `struct`, the base `struct` is the first item in
                // the list of bases (before any interfaces).
                //
                // This constraint also has the secondary effect of restricting
                // it so that a `struct` cannot multiply inherit from other
                // `struct` types.
                //
                if( inheritanceClauseIndex != 0 )
                {
                    getSink()->diagnose(inheritanceDecl, Diagnostics::baseStructMustBeListedFirst, decl, baseType);
                }
            }
            else
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfStructMustBeStructOrInterface, decl, baseType);
                continue;
            }

            // TODO: At this point we have the `baseDeclRef`
            // and could use it to perform further validity checks,
            // and/or to build up a more refined representation of
            // the inheritance graph for this type (e.g., a "class
            // precedence list").
            //
            // E.g., we can/should check that we aren't introducing
            // a circular inheritance relationship.

            _validateCrossModuleInheritance(decl, inheritanceDecl);
        }
    }

    bool SemanticsVisitor::isIntegerBaseType(BaseType baseType)
    {
        return (BaseTypeInfo::getInfo(baseType).flags & BaseTypeInfo::Flag::Integer) != 0;
    }

    bool SemanticsVisitor::isScalarIntegerType(Type* type)
    {
        auto basicType = as<BasicExpressionType>(type);
        if(!basicType)
            return false;

        return isIntegerBaseType(basicType->baseType);
    }

    void SemanticsVisitor::validateEnumTagType(Type* type, SourceLoc const& loc)
    {
        // Allow the built-in integer types.
        //
        if(isScalarIntegerType(type))
            return;

        // By default, don't allow other types to be used
        // as an `enum` tag type.
        //
        getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type);
    }

    void SemanticsDeclBasesVisitor::visitEnumDecl(EnumDecl* decl)
    {
        // An `enum` type can inherit from interfaces, and also
        // from a single "tag" type that must:
        //
        // * be a built-in integer type
        // * come first in the list of base types
        //
        Index inheritanceClauseCounter = 0;

        Type* tagType = nullptr;
        InheritanceDecl* tagTypeInheritanceDecl = nullptr;
        for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
        {
            Index inheritanceClauseIndex = inheritanceClauseCounter++;

            ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
            auto baseType = inheritanceDecl->base.type;

            // It is possible that there was an error in checking the base type
            // expression, and in such a case we shouldn't emit a cascading error.
            //
            if( auto baseErrorType = as<ErrorType>(baseType) )
            {
                continue;
            }

            auto baseDeclRefType = as<DeclRefType>(baseType);
            if( !baseDeclRefType )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfEnumMustBeIntegerOrInterface, decl, baseType);
                continue;
            }

            auto baseDeclRef = baseDeclRefType->declRef;
            if( auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>() )
            {
                _validateCrossModuleInheritance(decl, inheritanceDecl);
            }
            else if( auto baseStructDeclRef = baseDeclRef.as<StructDecl>() )
            {
                // To simplify the task of reading and maintaining code,
                // we require that when an `enum` declares an explicit
                // underlying tag type using an inheritance clause, that
                // type must be the first item in the list of bases.
                //
                // This constraint also has the secondary effect of restricting
                // it so that an `enum` can't possibly have multiple tag
                // types declared.
                //
                if( inheritanceClauseIndex != 0 )
                {
                    getSink()->diagnose(inheritanceDecl, Diagnostics::tagTypeMustBeListedFirst, decl, baseType);
                }
                else
                {
                    tagType = baseType;
                    tagTypeInheritanceDecl = inheritanceDecl;
                }

                // Note: we do *not* apply the code that validates
                // cross-module inheritance to a base that represnts
                // a tag type, because declaring a tag type for an
                // `enum` doesn't actually make it into a subtype
                // of the tag type, and thus doesn't violate the
                // rules when the tag type is `sealed`.
            }
            else
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfEnumMustBeIntegerOrInterface, decl, baseType);
                continue;
            }
        }

        // 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 = m_astBuilder->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 integer type...)
            //
            // For now we will just be harsh and require it
            // to be one of a few builtin types.
            validateEnumTagType(tagType, tagTypeInheritanceDecl->loc);

            // Note: The `InheritanceDecl` that introduces a tag
            // type isn't actually representing a super-type of
            // the `enum`, and things like name lookup need to
            // know to ignore that "inheritance" relationship.
            //
            // We add a modifier to the `InheritanceDecl` to ensure
            // that it can be detected and ignored by such steps.
            //
            addModifier(tagTypeInheritanceDecl, m_astBuilder->create<IgnoreForLookupModifier>());
        }
        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.
            Type* enumTypeType = getASTBuilder()->getEnumTypeType();

            InheritanceDecl* enumConformanceDecl = m_astBuilder->create<InheritanceDecl>();
            enumConformanceDecl->parentDecl = decl;
            enumConformanceDecl->loc = decl->loc;
            enumConformanceDecl->base.type = getASTBuilder()->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();
            witnessTable->baseType = enumConformanceDecl->base.type;
            witnessTable->witnessedType = enumTypeType;
            enumConformanceDecl->witnessTable = witnessTable;

            Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag");
            Decl* tagAssociatedTypeDecl = nullptr;
            if(auto enumTypeTypeDeclRefType = dynamicCast<DeclRefType>(enumTypeType))
            {
                if(auto enumTypeTypeInterfaceDecl = as<InterfaceDecl>(enumTypeTypeDeclRefType->declRef.getDecl()))
                {
                    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 witness for `__Tag` being satisfied by `tagType`
            witnessTable->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);
        }
    }

    void SemanticsDeclBodyVisitor::visitEnumDecl(EnumDecl* decl)
    {
        auto enumType = DeclRefType::create(m_astBuilder, makeDeclRef(decl));

        auto tagType = decl->tagType;

        // 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.
            //
            // TODO(tfoley): the case should grab its type  when
            // doing its own header checking, rather than rely on this...
            caseDecl->type.type = enumType;

            ensureDecl(caseDecl, DeclCheckState::Checked);
        }

        // 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->tagExpr)
            {
                // This tag has an initializer, so it should establish
                // the tag value for a successor case that doesn't
                // provide an explicit tag.

                IntVal* explicitTagVal = tryConstantFoldExpr(explicitTagValExpr, nullptr);
                if(explicitTagVal)
                {
                    if(auto constIntVal = as<ConstantIntVal>(explicitTagVal))
                    {
                        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.
                IntegerLiteralExpr* tagValExpr = m_astBuilder->create<IntegerLiteralExpr>();
                tagValExpr->loc = caseDecl->loc;
                tagValExpr->type = QualType(tagType);
                tagValExpr->value = defaultTag;

                caseDecl->tagExpr = 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++;
        }
    }

    void SemanticsDeclBodyVisitor::visitEnumCaseDecl(EnumCaseDecl* decl)
    {
        // An enum case had better appear inside an enum!
        //
        // TODO: Do we need/want to support generic cases some day?
        auto parentEnumDecl = as<EnumDecl>(decl->parentDecl);
        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->tagExpr)
        {
            initExpr = CheckTerm(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->tagExpr = initExpr;
        }
    }

    void SemanticsVisitor::ensureDeclBase(DeclBase* declBase, DeclCheckState state)
    {
        if(auto decl = as<Decl>(declBase))
        {
            ensureDecl(decl, state);
        }
        else if(auto declGroup = as<DeclGroup>(declBase))
        {
            for(auto dd : declGroup->decls)
            {
                ensureDecl(dd, state);
            }
        }
        else
        {
            SLANG_UNEXPECTED("unknown case for declaration");
        }
    }

    void SemanticsDeclHeaderVisitor::visitTypeDefDecl(TypeDefDecl* decl)
    {
        decl->type = CheckProperType(decl->type);
    }

    void SemanticsDeclHeaderVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl)
    {
        // global generic param only allowed in global scope
        auto program = as<ModuleDecl>(decl->parentDecl);
        if (!program)
            getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly);
    }

    void SemanticsDeclHeaderVisitor::visitAssocTypeDecl(AssocTypeDecl* decl)
    {
        // assoctype only allowed in an interface
        auto interfaceDecl = as<InterfaceDecl>(decl->parentDecl);
        if (!interfaceDecl)
            getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly);
    }

    void SemanticsDeclBodyVisitor::visitFunctionDeclBase(FunctionDeclBase* decl)
    {
        if (auto body = decl->body)
        {
            checkBodyStmt(body, decl);
        }
    }

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

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

    bool SemanticsVisitor::doGenericSignaturesMatch(
        GenericDecl*                    left,
        GenericDecl*                    right,
        GenericSubstitution**    outSubstRightToLeft)
    {
        // Our first goal here is to determine if `left` and
        // `right` have equivalent lists of explicit
        // generic parameters.
        //
        // Once we have determined that the explicit generic
        // parameters match, we will look at the constraints
        // placed on those parameters to see if they are
        // equivalent.
        //
        // We thus start by extracting the explicit parameters
        // and the constraints from each declaration.
        //
        List<Decl*> leftParams;
        List<GenericTypeConstraintDecl*> leftConstraints;
        getGenericParams(left, leftParams, leftConstraints);

        List<Decl*> rightParams;
        List<GenericTypeConstraintDecl*> rightConstraints;
        getGenericParams(right, rightParams, rightConstraints);

        // For there to be any hope of a match, the two decls
        // need to have the same number of explicit parameters.
        //
        Index paramCount = leftParams.getCount();
        if(paramCount != rightParams.getCount())
            return false;

        // Next we will walk through the parameters and look
        // for a pair-wise match.
        //
        for(Index pp = 0; pp < paramCount; ++pp)
        {
            Decl* leftParam = leftParams[pp];
            Decl* rightParam = rightParams[pp];

            if (auto leftTypeParam = as<GenericTypeParamDecl>(leftParam))
            {
                if (auto rightTypeParam = as<GenericTypeParamDecl>(rightParam))
                {
                    // Right now any two type parameters are a match.
                    // Names are irrelevant to matching, and any constraints
                    // on the type parameters are represented as implicit
                    // extra parameters of the generic.
                    //
                    // TODO: If we ever supported type parameters with
                    // higher kinds we might need to make a check here
                    // that the kind of each parameter matches (which
                    // would in a sense be a kind of recursive check
                    // of the generic signature of the parameter).
                    //
                    continue;
                }
            }
            else if (auto leftValueParam = as<GenericValueParamDecl>(leftParam))
            {
                if (auto rightValueParam = as<GenericValueParamDecl>(rightParam))
                {
                    // In this case we have two generic value parameters,
                    // and they should only be considered to match if
                    // they 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 (!leftValueParam->getType()->equals(rightValueParam->getType()))
                    {
                        // If the value parameters have non-matching types,
                        // then the full generic signatures do not match.
                        //
                        return false;
                    }

                    // Generic value parameters with the same type are
                    // always considered to match.
                    //
                    continue;
                }
            }

            // If we get to this point, then we have two parameters that
            // were of different syntatic categories (e.g., one type parameter
            // and one value parameter), so the signatures clearly don't match.
            //
            return false;
        }

        // At this point we know that the explicit generic parameters
        // of `left` and `right` are aligned, but we need to check
        // that the constraints that each declaration places on
        // its parameters match.
        //
        // A first challenge that arises is that `left` and `right`
        // will each express the constraints in terms of their
        // own parameters. For example, consider the following
        // declarations:
        //
        //      void foo1<T : IFoo>(T value);
        //      void foo2<U : IFoo>(U value);
        //
        // It is "obvious" to a human that the signatures here
        // match, but `foo1` has a constraint `T : IFoo` while
        // `foo2` has a constraint `U : IFoo`, and since `T`
        // and `U` are distinct `Decl`s, those constraints
        // are not obviously equivalent.
        //
        // We will work around this first issue by creating
        // a substitution taht lists all the parameters of
        // `left`, which we can use to specialize `right`
        // so that it aligns.
        //
        // In terms of the example above, this is like constructing
        // `foo2<T>` so that its constraint, after specialization,
        // looks like `T : IFoo`.
        //
        auto& substRightToLeft = *outSubstRightToLeft;
        substRightToLeft = createDummySubstitutions(left);
        substRightToLeft->genericDecl = right;

        // We should now be able to enumerate the constraints
        // on `right` in a way that uses the same type parameters
        // as `left`, using `rightDeclRef`.
        //
        // At this point a second problem arises: if/when we support
        // more flexibility in how generic parameter constraints are
        // specified, it will be possible for two declarations to
        // list the "same" constraints in very different ways.
        //
        // For example, if we support a `where` clause for separating
        // the constraints from the parameters, then the following
        // two declarations should have equivalent signatures:
        //
        //      void foo1<T>(T value)
        //          where T : IFoo
        //      { ... }
        //
        //      void foo2<T : IFoo>(T value)
        //      { ... }
        //
        // Similarly, if we allow for general compositions of interfaces
        // to be used as constraints, then there can be more than one
        // way to specify the same constraints:
        //
        //      void foo1<T : IFoo&IBar>(T value);
        //      void foo2<T : IBar&IFoo>(T value);
        //
        // Adding support for equality constraints in `where` clauses
        // also creates opportunities for multiple equivalent expressions:
        //
        //      void foo1<T,U>(...) where T.A == U.A;
        //      void foo2<T,U>(...) where U.A == T.A;
        //
        // A robsut version of the checking logic here should attempt
        // to *canonicalize* all of the constraints. Canonicalization
        // should involve putting constraints into a deterministic
        // order (e.g., for a generic with `<T,U>` all the constraints
        // on `T` should come before those on `U`), rewriting individual
        // constraints into a canonical form (e.g., `T : IFoo & IBar`
        // should turn into two constraints: `T : IFoo` and `T : IBar`),
        // etc.
        //
        // Once the constraints are in a canonical form we should be able
        // to test them for pairwise equivalent. As a safety measure we
        // could also try to test whether one set of constraints implies
        // the other (since implication in both directions should imply
        // equivalence, in which case our canonicalization had better
        // have produced the same result).
        //
        // For now we are taking a simpler short-cut by assuming
        // that constraints are already in a canonical form, which
        // is reasonable for now as the syntax only allows a single
        // constraint per parameter, specified on the parameter itself.
        //
        // Under the assumption of canonical constraints, we can
        // assume that different numbers of constraints must indicate
        // a signature mismatch.
        //
        Index constraintCount = leftConstraints.getCount();
        if(constraintCount != rightConstraints.getCount())
            return false;

        for (Index cc = 0; cc < constraintCount; ++cc)
        {
            // Note that we use a plain `Decl` pointer for the left
            // constraint, but need to use a `DeclRef` for the right
            // constraint so that we can take the substitution
            // arguments into account.
            //
            GenericTypeConstraintDecl* leftConstraint = leftConstraints[cc];
            DeclRef<GenericTypeConstraintDecl> rightConstraint(rightConstraints[cc], substRightToLeft);

            // For now, every constraint has the form `sub : sup`
            // to indicate that `sub` must be a subtype of `sup`.
            //
            // Two such constraints are equivalent if their `sub`
            // and `sup` types are pairwise equivalent.
            //
            auto leftSub = leftConstraint->sub;
            auto rightSub = getSub(m_astBuilder, rightConstraint);
            if(!leftSub->equals(rightSub))
                return false;

            auto leftSup = leftConstraint->sup;
            auto rightSup = getSup(m_astBuilder, rightConstraint);
            if(!leftSup->equals(rightSup))
                return false;
        }

        // If we have checked all of the (canonicalized) constraints
        // and found them to be pairwise equivalent then the two
        // generic signatures seem to match.
        //
        return true;
    }

    bool SemanticsVisitor::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.getCount();
        auto sndParamCount = sndParams.getCount();
        if (fstParamCount != sndParamCount)
            return false;

        for (Index 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(m_astBuilder, fstParam)->equals(getType(m_astBuilder, 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;
    }

    GenericSubstitution* SemanticsVisitor::createDummySubstitutions(
        GenericDecl* genericDecl)
    {
        GenericSubstitution* subst = m_astBuilder->create<GenericSubstitution>();
        subst->genericDecl = genericDecl;
        for (auto dd : genericDecl->members)
        {
            if (dd == genericDecl->inner)
                continue;

            if (auto typeParam = as<GenericTypeParamDecl>(dd))
            {
                auto type = DeclRefType::create(m_astBuilder, makeDeclRef(typeParam));
                subst->args.add(type);
            }
            else if (auto valueParam = as<GenericValueParamDecl>(dd))
            {
                auto val = m_astBuilder->create<GenericParamIntVal>(
                    makeDeclRef(valueParam));
                subst->args.add(val);
            }
            // TODO: need to handle constraints here?
        }
        return subst;
    }

    typedef Dictionary<Name*, CallableDecl*> TargetDeclDictionary;

    static void _addTargetModifiers(CallableDecl* decl, TargetDeclDictionary& ioDict)
    {
        if (auto specializedModifier = decl->findModifier<SpecializedForTargetModifier>())
        {
            // If it's specialized for target it should have a body...
            if (auto funcDecl = as<FunctionDeclBase>(decl))
            {
                SLANG_ASSERT(funcDecl->body);
            }
            Name* targetName = specializedModifier->targetToken.getName();

            ioDict.AddIfNotExists(targetName, decl);
        }
        else
        {
            for (auto modifier : decl->getModifiersOfType<TargetIntrinsicModifier>())
            {
                Name* targetName = modifier->targetToken.getName();
                ioDict.AddIfNotExists(targetName, decl);
            }

            auto funcDecl = as<FunctionDeclBase>(decl);
            if (funcDecl && funcDecl->body)
            {
                // Should only be one body if it isn't specialized for target.
                // Use nullptr for this scenario
                ioDict.AddIfNotExists(nullptr, decl);
            }
        }  
    }

    Result SemanticsVisitor::checkFuncRedeclaration(
        FuncDecl* newDecl,
        FuncDecl* oldDecl)
    {
        // There are a few different cases that this function needs
        // to check for:
        //
        // * If `newDecl` and `oldDecl` have different signatures such
        //   that they can always be distinguished at call sites, then
        //   they don't conflict and don't count as redeclarations.
        //
        // * If `newDecl` and `oldDecl` have matching signatures, but
        //   differ in return type (or other details that would affect
        //   compatibility), then the declarations conflict and an
        //   error needs to be diagnosed.
        //
        // * If `newDecl` and `oldDecl` have matching/compatible sigantures,
        //   but differ when it comes to target-specific overloading,
        //   then they can co-exist.
        //
        // * If `newDecl` and `oldDecl` have matching/compatible signatures
        //   and are specialized for the same target(s), then only
        //   one can have a body (in which case the other is a forward declaration),
        //   or else we have a redefinition error.

        auto newGenericDecl = as<GenericDecl>(newDecl->parentDecl);
        auto oldGenericDecl = as<GenericDecl>(oldDecl->parentDecl);

        // If one declaration is a prefix/postfix operator, and the
        // other is not a matching operator, then don't consider these
        // to be re-declarations.
        //
        // 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 (newDecl->hasModifier<PrefixModifier>() != oldDecl->hasModifier<PrefixModifier>())
            return SLANG_OK;
        if (newDecl->hasModifier<PostfixModifier>() != oldDecl->hasModifier<PostfixModifier>())
            return SLANG_OK;

        // If one is generic and the other isn't, then there is no match.
        if ((newGenericDecl != nullptr) != (oldGenericDecl != nullptr))
            return SLANG_OK;

        // 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> newDeclRef(newDecl, nullptr);
        DeclRef<FuncDecl> oldDeclRef(oldDecl, nullptr);

        // If we are working with generic functions, then we need to
        // consider if their generic signatures match.
        //
        if(newGenericDecl)
        {
            // If one declaration is generic, the other must be.
            // (This condition was already checked above)
            //
            SLANG_ASSERT(oldGenericDecl);

            // As part of checking if the generic signatures match,
            // we will produce a substitution that can be used to
            // reference `oldGenericDecl` with the generic parameters
            // substituted for those of `newDecl`.
            //
            // One way to think about it is that if we have these
            // declarations (ignore the name differences...):
            //
            //     // oldDecl:
            //     void foo1<T>(T x);
            //
            //     // newDecl:
            //     void foo2<U>(U x);
            //
            // Then we will compare the parameter types of `foo2`
            // against the specialization `foo1<U>`.
            //
            GenericSubstitution* subst = nullptr;
            if(!doGenericSignaturesMatch(newGenericDecl, oldGenericDecl, &subst))
                return SLANG_OK;

            oldDeclRef.substitutions.substitutions = subst;
        }

        // If the parameter signatures don't match, then don't worry
        if (!doFunctionSignaturesMatch(newDeclRef, oldDeclRef))
            return SLANG_OK;

        // 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 (!oldDecl->primaryDecl)
        {
            oldDecl->primaryDecl = oldDecl;
        }

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

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

        // 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(m_astBuilder, newDeclRef);
        auto prevResultType = getResultType(m_astBuilder, oldDeclRef);
        if (!resultType->equals(prevResultType))
        {
            // Bad redeclaration
            getSink()->diagnose(newDecl, Diagnostics::functionRedeclarationWithDifferentReturnType, newDecl->getName(), resultType, prevResultType);
            getSink()->diagnose(oldDecl, Diagnostics::seePreviousDeclarationOf, newDecl->getName());

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

        // 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.
       
        // If both of the declarations have a body, then there
        // is trouble, because we wouldn't know which one to
        // use during code generation.

        // Here to cover the 'bodies'/target_intrinsics, we find all the targets that
        // that are previously defined, and make sure the new definition
        // doesn't try and define what is already defined.
        {
            TargetDeclDictionary currentTargets;
            {
                CallableDecl* curDecl = newDecl->primaryDecl;
                while (curDecl)
                {
                    if (curDecl != newDecl)
                    {
                        _addTargetModifiers(curDecl, currentTargets);
                    }
                    curDecl = curDecl->nextDecl;
                }
            }

            // Add the targets for this new decl
            TargetDeclDictionary newTargets;
            _addTargetModifiers(newDecl, newTargets);

            bool hasConflict = false;
            for (auto& pair : newTargets)
            {
                Name* target = pair.Key;
                auto found = currentTargets.TryGetValue(target);
                if (found)
                {
                    // Redefinition
                    if (!hasConflict)
                    {
                        getSink()->diagnose(newDecl, Diagnostics::functionRedefinition, newDecl->getName());
                        hasConflict = true;
                    }

                    auto prevDecl = *found;
                    getSink()->diagnose(prevDecl, Diagnostics::seePreviousDefinitionOf, prevDecl->getName());
                }
            }

            if (hasConflict)
            {
                return SLANG_FAIL;
            }
        }

        // 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.
        //
        // While no diagnostics have been emitted, we return
        // a failure result from the operation to indicate
        // to the caller that they should stop looping over
        // declarations at this point.
        //
        return SLANG_FAIL;
    }

    Result SemanticsVisitor::checkRedeclaration(Decl* newDecl, Decl* oldDecl)
    {
        // If either of the declarations being looked at is generic, then
        // we want to consider the "inner" declaration instead when
        // making decisions about what to allow or not.
        //
        if(auto newGenericDecl = as<GenericDecl>(newDecl))
            newDecl = newGenericDecl->inner;
        if(auto oldGenericDecl = as<GenericDecl>(oldDecl))
            oldDecl = oldGenericDecl->inner;

        // Functions are special in that we can have many declarations
        // with the same name in a given scope, and it is possible
        // for them to co-exist as overloads, or even just be multiple
        // declarations of the same function (thanks to the inherited
        // legacy of C forward declarations).
        //
        // If both declarations are functions, we will check that
        // they are allowed to co-exist using these more nuanced rules.
        //
        if( auto newFuncDecl = as<FuncDecl>(newDecl) )
        {
            if(auto oldFuncDecl = as<FuncDecl>(oldDecl) )
            {
                // Both new and old declarations are functions,
                // so redeclaration may be valid.
                return checkFuncRedeclaration(newFuncDecl, oldFuncDecl);
            }
        }

        // For all other flavors of declaration, we do not
        // allow duplicate declarations with the same name.
        //
        // TODO: We might consider allowing some other cases
        // of overloading that can be safely disambiguated:
        //
        // * A type and a value (function/variable/etc.) of the same name can usually
        //   co-exist because we can distinguish which is needed by context.
        //
        // * Multiple generic types with the same name can co-exist
        //   if their generic parameter lists are sufficient to
        //   tell them apart at a use site.

        // We will diagnose a redeclaration error at the new declaration,
        // and point to the old declaration for context.
        //
        getSink()->diagnose(newDecl, Diagnostics::redeclaration, newDecl->getName());
        getSink()->diagnose(oldDecl, Diagnostics::seePreviousDeclarationOf, oldDecl->getName());
        return SLANG_FAIL;
    }


    void SemanticsVisitor::checkForRedeclaration(Decl* decl)
    {
        // We want to consider a "new" declaration in the context
        // of some parent/container declaration, and compare it
        // to pre-existing "old" declarations of the same name
        // in the same container.
        //
        auto newDecl = decl;
        auto parentDecl = decl->parentDecl;

        // Sanity check: there should always be a parent declaration.
        //
        SLANG_ASSERT(parentDecl);
        if (!parentDecl) return;

        // If the declaration is the "inner" declaration of a generic,
        // then we actually want to look one level up, because the
        // peers/siblings of the declaration will belong to the same
        // parent as the generic, not to the generic.
        //
        if( auto genericParentDecl = as<GenericDecl>(parentDecl) )
        {
            // Note: we need to check here to be sure `newDecl`
            // is the "inner" declaration and not one of the
            // generic parameters, or else we will end up
            // checking them at the wrong scope.
            //
            if( newDecl == genericParentDecl->inner )
            {
                newDecl = parentDecl;
                parentDecl = genericParentDecl->parentDecl;
            }
        }

        // We will now look for other declarations with
        // the same name in the same parent/container.
        //
        buildMemberDictionary(parentDecl);
        for (auto oldDecl = newDecl->nextInContainerWithSameName; oldDecl; oldDecl = oldDecl->nextInContainerWithSameName)
        {
            // For each matching declaration, we will check
            // whether the redeclaration should be allowed,
            // and emit an appropriate diagnostic if not.
            //
            Result checkResult = checkRedeclaration(newDecl, oldDecl);

            // The `checkRedeclaration` function will return a failure
            // status (whether or not it actually emitted a diagnostic)
            // if we should stop checking further redeclarations, because
            // the declaration in question has been dealt with fully.
            //
            if(SLANG_FAILED(checkResult))
                break;
        }
    }


    void SemanticsDeclHeaderVisitor::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;
        }
    }

    void SemanticsDeclBodyVisitor::visitParamDecl(ParamDecl* paramDecl)
    {
        auto typeExpr = paramDecl->type;

        // 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 = CheckTerm(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...).

            // A default argument value should not be allowed on an
            // `out` or `inout` parameter.
            //
            // TODO: we could relax this by requiring the expression
            // to yield an lvalue, but that seems like a feature
            // with limited practical utility (and an easy source
            // of confusing behavior).
            //
            // Note: the `InOutModifier` class inherits from `OutModifier`,
            // so we only need to check for the base case.
            //
            if(paramDecl->findModifier<OutModifier>())
            {
                getSink()->diagnose(initExpr, Diagnostics::outputParameterCannotHaveDefaultValue);
            }
        }
    }

    void SemanticsDeclHeaderVisitor::checkCallableDeclCommon(CallableDecl* decl)
    {
        for(auto paramDecl : decl->getParameters())
        {
            ensureDecl(paramDecl, DeclCheckState::ReadyForReference);
        }
    }

    void SemanticsDeclHeaderVisitor::visitFuncDecl(FuncDecl* funcDecl)
    {
        auto resultType = funcDecl->returnType;
        if(resultType.exp)
        {
            resultType = CheckProperType(resultType);
        }
        else
        {
            resultType = TypeExp(m_astBuilder->getVoidType());
        }
        funcDecl->returnType = resultType;

        checkCallableDeclCommon(funcDecl);
    }

    IntegerLiteralValue SemanticsVisitor::GetMinBound(IntVal* val)
    {
        if (auto constantVal = as<ConstantIntVal>(val))
            return constantVal->value;

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

    void SemanticsVisitor::maybeInferArraySizeForVariable(VarDeclBase* varDecl)
    {
        // Not an array?
        auto arrayType = as<ArrayExpressionType>(varDecl->type);
        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 type of the initializer an array type?
        if(auto arrayInitType = as<ArrayExpressionType>(initExpr->type))
        {
            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(
            m_astBuilder,
            arrayType->baseType,
            elementCount);
    }

    void SemanticsVisitor::validateArraySizeForVariable(VarDeclBase* varDecl)
    {
        auto arrayType = as<ArrayExpressionType>(varDecl->type);
        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 SemanticsDeclBasesVisitor::_validateExtensionDeclTargetType(ExtensionDecl* decl)
    {
        if (auto targetDeclRefType = as<DeclRefType>(decl->targetType))
        {
            // Attach our extension to that type as a candidate...
            if (auto aggTypeDeclRef = targetDeclRefType->declRef.as<AggTypeDecl>())
            {
                auto aggTypeDecl = aggTypeDeclRef.getDecl();

                getShared()->registerCandidateExtension(aggTypeDecl, decl);

                return;
            }
        }
        getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "an 'extension' can only extend a nominal type");
    }

    void SemanticsDeclBasesVisitor::visitExtensionDecl(ExtensionDecl* decl)
    {
        // We check the target type expression, and then validate
        // that the type it names is one that it makes sense
        // to extend.
        //
        decl->targetType = CheckProperType(decl->targetType);
        _validateExtensionDeclTargetType(decl);

        for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
        {
            ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
            auto baseType = inheritanceDecl->base.type;

            // It is possible that there was an error in checking the base type
            // expression, and in such a case we shouldn't emit a cascading error.
            //
            if( auto baseErrorType = as<ErrorType>(baseType) )
            {
                continue;
            }

            // An `extension` can only introduce inheritance from `interface` types.
            //
            // TODO: It might in theory make sense to allow an `extension` to
            // introduce a non-`interface` base if we decide that an `extension`
            // within the same module as the type it extends counts as just
            // a continuation of the type's body (like a `partial class` in C#).
            //
            auto baseDeclRefType = as<DeclRefType>(baseType);
            if( !baseDeclRefType )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfExtensionMustBeInterface, decl, baseType);
                continue;
            }

            auto baseDeclRef = baseDeclRefType->declRef;
            auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>();
            if( !baseInterfaceDeclRef )
            {
                getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfExtensionMustBeInterface, decl, baseType);
                continue;
            }

            // TODO: At this point we have the `baseInterfaceDeclRef`
            // and could use it to perform further validity checks,
            // and/or to build up a more refined representation of
            // the inheritance graph for this extension (e.g., a "class
            // precedence list").
            //
            // E.g., we can/should check that we aren't introducing
            // an inheritance relationship that already existed
            // on the type as originally declared.

            _validateCrossModuleInheritance(decl, inheritanceDecl);
        }
    }

    Type* SemanticsVisitor::calcThisType(DeclRef<Decl> declRef)
    {
        if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() )
        {
            // In the body of an `interface`, a `This` type
            // refers to the concrete type that will eventually
            // conform to the interface and fill in its
            // requirements.
            //
            ThisType* thisType = m_astBuilder->create<ThisType>();
            thisType->interfaceDeclRef = interfaceDeclRef;
            return thisType;
        }
        else if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>())
        {
            // In the body of an ordinary aggregate type,
            // such as a `struct`, the `This` type just
            // refers to the type itself.
            //
            // TODO: If/when we support `class` types
            // with inheritance, then `This` inside a class
            // would need to refer to the eventual concrete
            // type, much like the `interface` case above.
            //
            return DeclRefType::create(m_astBuilder, aggTypeDeclRef);
        }
        else if (auto extDeclRef = declRef.as<ExtensionDecl>())
        {
            // In the body of an `extension`, the `This`
            // type refers to the type being extended.
            //
            // Note: we currently have this loop back
            // around through `calcThisType` for the
            // type being extended, rather than just
            // using it directly. This makes a difference
            // for polymorphic types like `interface`s,
            // and there are reasonable arguments for
            // the validity of either option.
            //
            // Does `extension IFoo` mean extending
            // exactly the type `IFoo` (an existential,
            // which could at runtime be a value of
            // any type conforming to `IFoo`), or does
            // it implicitly extend every type that
            // conforms to `IFoo`? The difference is
            // significant, and we need to make a choice
            // sooner or later.
            //
            ensureDecl(extDeclRef, DeclCheckState::CanUseExtensionTargetType);
            auto targetType = getTargetType(m_astBuilder, extDeclRef);
            return calcThisType(targetType);
        }
        else
        {
            return nullptr;
        }
    }

    Type* SemanticsVisitor::calcThisType(Type* type)
    {
        if( auto declRefType = as<DeclRefType>(type) )
        {
            return calcThisType(declRefType->declRef);
        }
        else
        {
            return type;
        }
    }

    Type* SemanticsVisitor::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 = as<GenericDecl>(parent);
        if (genericParent)
        {
            parent = genericParent->parentDecl;
        }

        // The result type for a constructor is whatever `This` would
        // refer to in the body of the outer declaration.
        //
        auto thisType = calcThisType(makeDeclRef(parent));
        if( !thisType )
        {
            getSink()->diagnose(decl, Diagnostics::initializerNotInsideType);
            thisType = m_astBuilder->getErrorType();
        }
        return thisType;
    }

    void SemanticsDeclHeaderVisitor::visitConstructorDecl(ConstructorDecl* decl)
    {
        // We need to compute the result tyep for this declaration,
        // since it wasn't filled in for us.
        decl->returnType.type = findResultTypeForConstructorDecl(decl);

        checkCallableDeclCommon(decl);
    }

    void SemanticsDeclHeaderVisitor::visitAbstractStorageDeclCommon(ContainerDecl* decl)
    {
        // If we have a subscript or property 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;
        //      property x : Y;
        //
        // becomes:
        //
        //      subscript(uint index) -> T { get; }
        //      property x : Y { get; }
        //

        bool anyAccessors = decl->getMembersOfType<AccessorDecl>().isNonEmpty();

        if(!anyAccessors)
        {
            GetterDecl* getterDecl = m_astBuilder->create<GetterDecl>();
            getterDecl->loc = decl->loc;

            getterDecl->parentDecl = decl;
            decl->members.add(getterDecl);
        }
    }

    void SemanticsDeclHeaderVisitor::visitSubscriptDecl(SubscriptDecl* decl)
    {
        decl->returnType = CheckUsableType(decl->returnType);

        visitAbstractStorageDeclCommon(decl);

        checkCallableDeclCommon(decl);
    }

    void SemanticsDeclHeaderVisitor::visitPropertyDecl(PropertyDecl* decl)
    {
        decl->type = CheckUsableType(decl->type);
        visitAbstractStorageDeclCommon(decl);
    }

    Type* SemanticsDeclHeaderVisitor::_getAccessorStorageType(AccessorDecl* decl)
    {
        auto parentDecl = decl->parentDecl;
        if (auto parentSubscript = as<SubscriptDecl>(parentDecl))
        {
            ensureDecl(parentSubscript, DeclCheckState::CanUseTypeOfValueDecl);
            return parentSubscript->returnType;
        }
        else if (auto parentProperty = as<PropertyDecl>(parentDecl))
        {
            ensureDecl(parentProperty, DeclCheckState::CanUseTypeOfValueDecl);
            return parentProperty->type.type;
        }
        else
        {
            return getASTBuilder()->getErrorType();
        }
    }

    void SemanticsDeclHeaderVisitor::_visitAccessorDeclCommon(AccessorDecl* decl)
    {
        // An accessor must appear nested inside a subscript or property declaration.
        //
        auto parentDecl = decl->parentDecl;
        if (as<SubscriptDecl>(parentDecl))
        {}
        else if (as<PropertyDecl>(parentDecl))
        {}
        else
        {
            getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty);
        }
    }

    void SemanticsDeclHeaderVisitor::visitAccessorDecl(AccessorDecl* decl)
    {
        _visitAccessorDeclCommon(decl);

        // Note: This subroutine is used by both `get`
        // and `ref` accessors, but is bypassed by
        // `set` accessors (which use `visitSetterDecl`
        // intead).

        // Accessors (other than setters) don't support
        // parameters.
        //
        if( decl->getParameters().getCount() != 0 )
        {
            getSink()->diagnose(decl, Diagnostics::nonSetAccessorMustNotHaveParams);
        }

        // By default, the return type of an accessor is treated as
        // the type of the abstract storage location being accessed.
        //
        // A `ref`  accessor currently relies on this logic even though
        // it isn't quite correct, because we don't have support
        // for by-reference return values today. This is a non-issue
        // for now because we don't support user-defined `ref`
        // accessors yet.
        //
        // TODO: Once we can support the by-reference return value
        // correctly *or* we can move to something like a coroutine-based
        // `modify` accessor (a la Swift), we should split out
        // handling of `RefAccessorDecl` and only use this routine
        // for `GetterDecl`s.
        //
        decl->returnType.type = _getAccessorStorageType(decl);
    }

    void SemanticsDeclHeaderVisitor::visitSetterDecl(SetterDecl* decl)
    {
        // Make sure to invoke the common checking logic for all accessors.
        _visitAccessorDeclCommon(decl);

        // A `set` accessor always returns `void`.
        //
        decl->returnType.type = getASTBuilder()->getVoidType();

        // A setter always receives a single value representing
        // the new value to set into the storage.
        //
        // The user may declare that parameter explicitly and
        // thereby control its name, or they can declare no
        // parmaeters and allow the compiler to synthesize one
        // names `newValue`.
        //
        ParamDecl* newValueParam = nullptr;
        auto params = decl->getParameters();
        if( params.getCount() >= 1 )
        {
            // If the user declared an explicit parameter
            // then that is the one that will represent
            // the new value.
            //
            newValueParam = params.getFirst();

            if( params.getCount() > 1 )
            {
                // If the user declared more than one explicit
                // parameter, then that is an error.
                //
                getSink()->diagnose(params[1], Diagnostics::setAccessorMayNotHaveMoreThanOneParam);
            }
        }
        else
        {
            // If the user didn't declare any explicit parameters,
            // then we create an implicit one and add it into
            // the AST.
            //
            newValueParam = m_astBuilder->create<ParamDecl>();
            newValueParam->nameAndLoc.name = getName("newValue");
            newValueParam->nameAndLoc.loc = decl->loc;

            newValueParam->parentDecl = decl;
            decl->members.add(newValueParam);
        }

        // The new-value parameter is expected to have the
        // same type as the abstract storage that the
        // accessor is setting.
        //
        auto newValueType = _getAccessorStorageType(decl);

        // It is allowed and encouraged for the programmer
        // to leave off the type on the new-value parameter,
        // in which case we will set it to the expected
        // type automatically.
        //
        if( !newValueParam->type.exp )
        {
            newValueParam->type.type = newValueType;
        }
        else
        {
            // If the user *did* give the new-value parameter
            // an explicit type, then we need to check it
            // and then enforce that it matches what we expect.
            //
            auto actualType = CheckProperType(newValueParam->type);

            if(as<ErrorType>(actualType))
            {}
            else if(actualType->equals(newValueType))
            {}
            else
            {
                getSink()->diagnose(newValueParam, Diagnostics::setAccessorParamWrongType, newValueParam, actualType, newValueType);
            }
        }
    }

    GenericDecl* SemanticsVisitor::GetOuterGeneric(Decl* decl)
    {
        auto parentDecl = decl->parentDecl;
        if (!parentDecl) return nullptr;
        auto parentGeneric = as<GenericDecl>(parentDecl);
        return parentGeneric;
    }

    DeclRef<ExtensionDecl> SemanticsVisitor::ApplyExtensionToType(
        ExtensionDecl*  extDecl,
        Type*    type)
    {
        DeclRef<ExtensionDecl> extDeclRef = makeDeclRef(extDecl);

        // If the extension is a generic extension, then we
        // need to infer type arguments 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>();
            }

            // Construct 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.
        Type* targetType = getTargetType(m_astBuilder, 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 = as<DeclRefType>(targetType))
        {
            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 = as<DeclRefType>(type))
                {
                    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 = as<ThisTypeSubstitution>(appInterfaceDeclRef.substitutions.substitutions))
                            {
                                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(!as<ThisTypeSubstitution>(targetInterfaceDeclRef.substitutions.substitutions));

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

                                    targetType = DeclRefType::create(m_astBuilder,
                                        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.

                                    ThisTypeSubstitution* newExtSubst = m_astBuilder->create<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;
    }

    QualType SemanticsVisitor::GetTypeForDeclRef(DeclRef<Decl> declRef, SourceLoc loc)
    {
        Type* typeResult = nullptr;
        return getTypeForDeclRef(
            m_astBuilder,
            this,
            getSink(),
            declRef,
            &typeResult,
            loc);
    }

    void SemanticsVisitor::importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl)
    {
        // If we've imported this one already, then
        // skip the step where we modify the current scope.
        auto& importedModulesList = getShared()->importedModulesList;
        auto& importedModulesSet = getShared()->importedModulesSet;
        if (importedModulesSet.Contains(moduleDecl))
        {
            return;
        }
        importedModulesList.add(moduleDecl);
        importedModulesSet.Add(moduleDecl);

        // Create a new sub-scope to wire the module
        // into our lookup chain.
        auto subScope = getASTBuilder()->create<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);
        }
    }

    void SemanticsDeclHeaderVisitor::visitImportDecl(ImportDecl* decl)
    {
        // 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 importedModule = findOrImportModule(
            getLinkage(),
            name,
            decl->moduleNameAndLoc.loc,
            getSink());

        // If we didn't find a matching module, then bail out
        if (!importedModule)
            return;

        // Record the module that was imported, so that we can use
        // it later during code generation.
        auto importedModuleDecl = importedModule->getModuleDecl();
        decl->importedModuleDecl = importedModuleDecl;

        // Add the declarations from the imported module into the scope
        // that the `import` declaration is set to extend.
        //
        importModuleIntoScope(scope, importedModuleDecl);

        // Record the `import`ed module (and everything it depends on)
        // as a dependency of the module we are compiling.
        if(auto module = getModule(decl))
        {
            module->addModuleDependency(importedModule);
        }
    }

    void SemanticsDeclHeaderVisitor::visitUsingDecl(UsingDecl* decl)
    {
        // First, we need to look up whatever the argument of the `using`
        // declaration names.
        //
        decl->arg = CheckTerm(decl->arg);

        // Next, we want to ensure that whatever is being named by `decl->arg`
        // is a namespace (or a module, since modules are namespace-like).
        //
        // TODO: The logic here assumes that we can't have multiple `NamespaceDecl`s
        // with the same name in scope, but that assumption is only valid in the
        // context of a single module (where we deduplicate `namespace`s during
        // parsing). If a user `import`s multiple modules that all have namespaces
        // of the same name, it would be possible for `decl->arg` to be overloaded.
        // In that case we should really iterate over all the entities that are
        // named and import any that are namespace-like.
        //
        NamespaceDeclBase* namespaceDecl = nullptr;
        if( auto declRefExpr = as<DeclRefExpr>(decl->arg) )
        {
            if( auto namespaceDeclRef = declRefExpr->declRef.as<NamespaceDeclBase>() )
            {
                SLANG_ASSERT(!namespaceDeclRef.substitutions.substitutions);
                namespaceDecl = namespaceDeclRef.getDecl();
            }
        }
        if( !namespaceDecl )
        {
            getSink()->diagnose(decl->arg, Diagnostics::expectedANamespace, decl->arg->type);
            return;
        }

        // Once we have identified the namespace to bring into scope,
        // we need to create a new sibling sub-scope to add to the
        // lookup scope that was in place when the `using` was parsed.
        //
        // Subsequent lookup in that scope will walk through our new
        // sub-scope and see the namespace.
        //
        // TODO: If we update the `containerDecl` in a scope to allow
        // for a more general `DeclRef`, or even a full `DeclRefExpr`,
        // then it would be possible for `using` to apply to more kinds
        // of entities than just namespaces.
        //
        auto scope = decl->scope;
        auto subScope = getASTBuilder()->create<Scope>();
        subScope->containerDecl = namespaceDecl;
        subScope->nextSibling = scope->nextSibling;
        scope->nextSibling = subScope;
    }

        /// Get a reference to the candidate extension list for `typeDecl` in the given dictionary
        ///
        /// Note: this function creates an empty list of candidates for the given type if
        /// a matching entry doesn't exist already.
        ///
    static List<ExtensionDecl*>& _getCandidateExtensionList(
        AggTypeDecl* typeDecl,
        Dictionary<AggTypeDecl*, RefPtr<CandidateExtensionList>>& mapTypeToCandidateExtensions)
    {
        RefPtr<CandidateExtensionList> entry;
        if( !mapTypeToCandidateExtensions.TryGetValue(typeDecl, entry) )
        {
            entry = new CandidateExtensionList();
            mapTypeToCandidateExtensions.Add(typeDecl, entry);
        }
        return entry->candidateExtensions;
    }

    List<ExtensionDecl*> const& SharedSemanticsContext::getCandidateExtensionsForTypeDecl(AggTypeDecl* decl)
    {
        // We are caching the lists of candidate extensions on the shared
        // context, so we will only build the lists if they either have
        // not been built before, or if some code caused the lists to
        // be invalidated.
        //
        // TODO: Similar to the rebuilding of lookup tables in `ContainerDecl`s,
        // we probably want to optimize this logic to gracefully handle new
        // extensions encountered during checking instead of tearing the whole
        // thing down. For now this potentially-quadratic behavior is acceptable
        // because there just aren't that many extension declarations being used.
        //
        if( !m_candidateExtensionListsBuilt )
        {
            m_candidateExtensionListsBuilt = true;

            // We need to make sure that all extensions that were declared
            // as part of our standard-library modules are always visible,
            // even if they are not explicit `import`ed into user code.
            //
            for( auto module : getSession()->stdlibModules )
            {
                _addCandidateExtensionsFromModule(module->getModuleDecl());
            }

            // There are two primary modes in which the `SharedSemanticsContext`
            // gets used.
            //
            // In the first mode, we are checking an entire `ModuelDecl`, and we
            // need to always check things from the "point of view" of that module
            // (so that the extensions that should be visible are based on what
            // that module can access via `import`s).
            //
            // In the second mode, we are checking code related to API interactions
            // by the user (e.g., parsing a type from a string, specializing an
            // entry point to type arguments, etc.). In these cases there is no
            // clear module that should determine the point of view for looking
            // up extensions, and we instead need/want to consider any extensions
            // from all modules loaded into the linkage.
            //
            // We differentiate these cases based on whether a "primary" module
            // was set at the time the `SharedSemanticsContext` was constructed.
            //
            if( m_module )
            {
                // We have a "primary" module that is being checked, and we should
                // look up extensions based on what would be visible to that
                // module.
                //
                // We need to consider the extensions declared in the module itself,
                // along with everything the module imported.
                //
                // Note: there is an implicit assumption here that the `importedModules`
                // member on the `SharedSemanticsContext` is accurate in this case.
                //
                _addCandidateExtensionsFromModule(m_module->getModuleDecl());
                for( auto moduleDecl : this->importedModulesList )
                {
                    _addCandidateExtensionsFromModule(moduleDecl);
                }
            }
            else
            {
                // We are in one of the many ad hoc checking modes where we really
                // want to resolve things based on the totality of what is
                // available/defined within the current linkage.
                //
                for( auto module : m_linkage->loadedModulesList )
                {
                    _addCandidateExtensionsFromModule(module->getModuleDecl());
                }
            }
        }

        // Once we are sure that the dictionary-of-arrays of extensions
        // has been populated, we return to the user the entry they
        // asked for.
        //
        return _getCandidateExtensionList(decl, m_mapTypeDeclToCandidateExtensions);
    }

    void SharedSemanticsContext::registerCandidateExtension(AggTypeDecl* typeDecl, ExtensionDecl* extDecl)
    {
        // The primary cache of extension declarations is on the `ModuleDecl`.
        // We will add the `extDecl` to the cache for the module it belongs to.
        //
        // We can be sure that the resulting cache won't have lifetime issues,
        // because all the extensions it contains are owned by the module itself,
        // and the types used as keys had to be reachable/referenceable from the
        // code inside the module for the given `extDecl` to extend them.
        //
        auto moduleDecl = getModuleDecl(extDecl);
        _getCandidateExtensionList(typeDecl, moduleDecl->mapTypeToCandidateExtensions).add(extDecl);

        // Because we've loaded a new extension, we need to invalidate whatever
        // information the `SharedSemanticsContext` had cached about loaded
        // extensions, and force it to rebuild its cache to include the
        // new extension we just added.
        //
        // TODO: We should probably just go ahead and add `extDecl` directly
        // into the appropriate entry here, and do a similar step on each
        // `import`.
        //
        m_candidateExtensionListsBuilt = false;
        m_mapTypeDeclToCandidateExtensions.Clear();
    }

    void SharedSemanticsContext::_addCandidateExtensionsFromModule(ModuleDecl* moduleDecl)
    {
        for( auto& entry : moduleDecl->mapTypeToCandidateExtensions )
        {
            auto& list = _getCandidateExtensionList(entry.Key, m_mapTypeDeclToCandidateExtensions);
            list.addRange(entry.Value->candidateExtensions);
        }
    }

    List<ExtensionDecl*> const& getCandidateExtensions(
        DeclRef<AggTypeDecl> const& declRef,
        SemanticsVisitor*           semantics)
    {
        auto decl = declRef.getDecl();
        auto shared = semantics->getShared();
        return shared->getCandidateExtensionsForTypeDecl(decl);
    }

    void _foreachDirectOrExtensionMemberOfType(
        SemanticsVisitor*               semantics,
        DeclRef<ContainerDecl> const&   containerDeclRef,
        SyntaxClassBase const&          syntaxClass,
        void                            (*callback)(DeclRefBase, void*),
        void const*                     userData)
    {
        // We are being asked to invoke the given callback on
        // each direct member of `containerDeclRef`, along with
        // any members added via `extension` declarations, that
        // have the correct AST node class (`syntaxClass`).
        //
        // We start with the direct members.
        //
        for( auto memberDeclRef : getMembers(containerDeclRef) )
        {
            if( memberDeclRef.decl->getClass().isSubClassOfImpl(syntaxClass) )
            {
                callback(memberDeclRef, (void*)userData);
            }
        }

        // Next, in the case wher ethe type can be subject to extensions,
        // we loop over the applicable extensions and their member.s
        //
        if(auto aggTypeDeclRef = containerDeclRef.as<AggTypeDecl>())
        {
            auto aggType = DeclRefType::create(semantics->getASTBuilder(), aggTypeDeclRef);
            for(auto extDecl : getCandidateExtensions(aggTypeDeclRef, semantics))
            {
                // Note that `extDecl` may have been declared for a type
                // base on the declaration that `aggTypeDeclRef` refers
                // to, but that does not guarantee that it applies to
                // the type itself. E.g., we might have an extension of
                // `vector<float, N>` for any `N`, but the current type is
                // `vector<int, 2>` so that the extension doesn't match.
                //
                // In order to make sure that we don't enumerate members
                // that don't make sense in context, we must apply
                // the extension to the type and see if we succeed in
                // making a match.
                //
                auto extDeclRef = ApplyExtensionToType(semantics, extDecl, aggType);
                if(!extDeclRef)
                    continue;

                for( auto memberDeclRef : getMembers(extDeclRef) )
                {
                    if( memberDeclRef.decl->getClass().isSubClassOfImpl(syntaxClass) )
                    {
                        callback(memberDeclRef, (void*)userData);
                    }
                }
            }
        }
    }


    static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared)
    {
        switch(state)
        {
        case DeclCheckState::ModifiersChecked:
            SemanticsDeclModifiersVisitor(shared).dispatch(decl);
            break;

        case DeclCheckState::SignatureChecked:
            SemanticsDeclHeaderVisitor(shared).dispatch(decl);
            break;

        case DeclCheckState::ReadyForReference:
            SemanticsDeclRedeclarationVisitor(shared).dispatch(decl);
            break;

        case DeclCheckState::ReadyForLookup:
            SemanticsDeclBasesVisitor(shared).dispatch(decl);
            break;

        case DeclCheckState::ReadyForConformances:
            SemanticsDeclConformancesVisitor(shared).dispatch(decl);
            break;

        case DeclCheckState::Checked:
            SemanticsDeclBodyVisitor(shared).dispatch(decl);
            break;
        }
    }

}
back to top