https://github.com/shader-slang/slang
Raw File
Tip revision: d06a78d935b2743494d47ed5cd3f36e38ac9c5ac authored by Yong He on 04 February 2022, 03:17:30 UTC
Add gfx interop to allow more direct D3D12 usage scenarios. (#2117)
Tip revision: d06a78d
slang-check-overload.cpp
// slang-check-overload.cpp
#include "slang-check-impl.h"

#include "slang-lookup.h"
#include "slang-ast-print.h"

// This file implements semantic checking logic related
// to resolving overloading call operations, by checking
// the applicability and relative priority of various candidates.

namespace Slang
{
    SemanticsVisitor::ParamCounts SemanticsVisitor::CountParameters(FilteredMemberRefList<ParamDecl> params)
    {
        ParamCounts counts = { 0, 0 };
        for (auto param : params)
        {
            counts.allowed++;

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

    SemanticsVisitor::ParamCounts SemanticsVisitor::CountParameters(DeclRef<GenericDecl> genericRef)
    {
        ParamCounts counts = { 0, 0 };
        for (auto m : genericRef.getDecl()->members)
        {
            if (auto typeParam = as<GenericTypeParamDecl>(m))
            {
                counts.allowed++;
                if (!typeParam->initType.Ptr())
                {
                    counts.required++;
                }
            }
            else if (auto valParam = as<GenericValueParamDecl>(m))
            {
                counts.allowed++;
                if (!valParam->initExpr)
                {
                    counts.required++;
                }
            }
        }
        return counts;
    }

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

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

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

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

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

        return false;
    }

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

        auto decl = candidate.item.declRef.decl;

        if(auto prefixExpr = as<PrefixExpr>(expr))
        {
            if(decl->hasModifier<PrefixModifier>())
                return true;

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

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

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

            return false;
        }
        else
        {
            return true;
        }
    }

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

        // The basic idea here is that we need to check that the
        // arguments to a generic application (e.g., `F<A1, A2, ...>`)
        // have the right "type," which in this context means
        // checking that:
        //
        // * The argument for any generic type parameter is a (proper) type.
        //
        // * The argument for any generic value parameter is a
        //   specialization-time constant value of the appropriate type.
        //
        // Some additional checks are *not* handled at this point:
        //
        // * We don't check that a type argument actually conforms to
        //   the constraints on the parameter.
        //
        // Along the way we will build up a `GenericSubstitution`
        // to represent the arguments that have been coerced to
        // appropriate forms.
        //
        auto genSubst = m_astBuilder->create<GenericSubstitution>();
        candidate.subst = genSubst;
        auto& checkedArgs = genSubst->args;

        // Rather than bail out as soon as we hit a problem,
        // we are going to process *all* of the parameters of the
        // generic and place suitable arguments into the `checkedArgs`
        // array. This is important so that we don't cause crashes
        // in cases where the arguments fail this step of checking,
        // but we decide to proceed with subsequent steps (e.g.,
        // because the candidate we are trying here is the *only*
        // candidate).
        //
        bool success = true;

        Index aa = 0;
        for (auto memberRef : getMembers(genericDeclRef))
        {
            if (auto typeParamRef = memberRef.as<GenericTypeParamDecl>())
            {
                // We have a type parameter, and we expect to find
                // a type argument.
                //
                TypeExp typeArg;
                if( aa >= context.argCount )
                {
                    // If we have run out of arguments, then we definitely
                    // fail checking (in principle this should have been
                    // checked already by an earlier step).
                    //
                    success = false;
                }
                else
                {
                    // If we have at least one argument left, we grab
                    // it and try to coerce it to a proper type. The
                    // manner in which we handle the coercion depends
                    // on whether we are "just trying" the candidate
                    // (so a failure would rule out the candidate, but
                    // shouldn't be reported to the user), or are doing
                    // the checking "for real" in which case any errors
                    // we run into need to be reported.
                    //
                    auto arg = context.getArg(aa++);
                    if (context.mode == OverloadResolveContext::Mode::JustTrying)
                    {
                        typeArg = tryCoerceToProperType(TypeExp(arg));
                    }
                    else
                    {
                        typeArg = CoerceToProperType(TypeExp(arg));
                    }
                }

                // If we failed to get a valid type (either because
                // there was no matching argument, or because the
                // "just trying" coercion failed), then we create
                // an error type to stand in for the argument
                //
                if( !typeArg.type )
                {
                    typeArg.type = m_astBuilder->getErrorType();
                    success = false;
                }

                checkedArgs.add(typeArg.type);
            }
            else if (auto valParamRef = memberRef.as<GenericValueParamDecl>())
            {
                // The case for a generic value parameter is similar to that
                // for a generic type parameter.
                //
                Expr* arg = nullptr;
                if( aa >= context.argCount )
                {
                    // If there are no arguments left to consume, then
                    // we have a definite failure.
                    //
                    success = false;
                }
                else
                {
                    // If we have an argument then we need to coerce it
                    // to the type of the parameter (and fail if the
                    // coercion is not possible)
                    //
                    arg = context.getArg(aa++);
                    if (context.mode == OverloadResolveContext::Mode::JustTrying)
                    {
                        ConversionCost cost = kConversionCost_None;
                        if (!canCoerce(getType(m_astBuilder, valParamRef), arg->type, arg, &cost))
                        {
                            success = false;
                        }
                        candidate.conversionCostSum += cost;
                    }
                    else
                    {
                        arg = coerce(getType(m_astBuilder, valParamRef), arg);
                    }
                }

                // If we have an argument to work with, then we will
                // try to extract its speicalization-time constant value.
                //
                // TODO: This is one of the places where we will need to
                // generalize in order to support generic value parameters
                // with types other than `int`.
                //
                Val* val = nullptr;
                if( arg )
                {
                    val = ExtractGenericArgInteger(arg, context.mode == OverloadResolveContext::Mode::JustTrying ? nullptr : getSink());
                }

                // If any of the above checking steps fail and we don't
                // have a value to work with here, we will instead
                // use an "error" value to stand in for the argument.
                //
                if( !val )
                {
                    val = m_astBuilder->create<ErrorIntVal>();
                }
                checkedArgs.add(val);
            }
            else
            {
                continue;
            }
        }

        // Once we are done processing the parameters of the generic,
        // we will have build up a usable `checkedArgs` array and
        // can return to the caller a report of whether we
        // were successful or not.
        //
        return success;
    }

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

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

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

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

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

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

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

    bool isEffectivelyMutating(CallableDecl* decl)
    {
        if(decl->hasModifier<MutatingAttribute>())
            return true;

        if(decl->hasModifier<NonmutatingAttribute>())
            return false;

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

        return false;
    }

    bool SemanticsVisitor::TryCheckOverloadCandidateDirections(
        OverloadResolveContext&     context,
        OverloadCandidate const&    candidate)
    {
        if(candidate.flavor != OverloadCandidate::Flavor::Func)
            return true;

        auto funcDeclRef = candidate.item.declRef.as<CallableDecl>();
        SLANG_ASSERT(funcDeclRef);

        // Note: This operation was originally introduced as
        // a place to add checking around l-value-ness of arguments
        // and parameters, but currently that checking is being
        // done in other places.
        //
        // For now we will only use this step to check the
        // mutability of the `this` parameter where necessary.
        //
        if(!isEffectivelyStatic(funcDeclRef.getDecl()))
        {
            if(isEffectivelyMutating(funcDeclRef.getDecl()))
            {
                if(context.baseExpr && !context.baseExpr->type.isLeftValue)
                {
                    if(context.mode == OverloadResolveContext::Mode::ForReal)
                    {
                        getSink()->diagnose(context.loc, Diagnostics::mutatingMethodOnImmutableValue, funcDeclRef.getName());
                        maybeDiagnoseThisNotLValue(context.baseExpr);
                    }
                    return false;
                }
            }
        }

        return true;
    }

    bool SemanticsVisitor::TryCheckOverloadCandidateConstraints(
        OverloadResolveContext&		context,
        OverloadCandidate const&	candidate)
    {
        // We only need this step for generics, so always succeed on
        // everything else.
        if(candidate.flavor != OverloadCandidate::Flavor::Generic)
            return true;

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

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

        auto subst = as<GenericSubstitution>(candidate.subst);
        SLANG_ASSERT(subst);

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

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

            auto sub = getSub(m_astBuilder, constraintDeclRef);
            auto sup = getSup(m_astBuilder, constraintDeclRef);

            auto subTypeWitness = tryGetSubtypeWitness(sub, sup);
            if(subTypeWitness)
            {
                subst->args.add(subTypeWitness);
            }
            else
            {
                if(context.mode != OverloadResolveContext::Mode::JustTrying)
                {
                    getSink()->diagnose(context.loc, Diagnostics::typeArgumentDoesNotConformToInterface, sub, sup);
                }
                return false;
            }
        }

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

    void SemanticsVisitor::TryCheckOverloadCandidate(
        OverloadResolveContext&		context,
        OverloadCandidate&			candidate)
    {
        if (!TryCheckOverloadCandidateArity(context, candidate))
            return;

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

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

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

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

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

    Expr* SemanticsVisitor::createGenericDeclRef(
        Expr*                baseExpr,
        Expr*                originalExpr,
        GenericSubstitution* subst)
    {
        auto baseDeclRefExpr = as<DeclRefExpr>(baseExpr);
        if (!baseDeclRefExpr)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration");
            return CreateErrorExpr(originalExpr);
        }
        auto baseGenericRef = baseDeclRefExpr->declRef.as<GenericDecl>();
        if (!baseGenericRef)
        {
            SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration");
            return CreateErrorExpr(originalExpr);
        }

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

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

        Expr* base = nullptr;
        if (auto mbrExpr = as<MemberExpr>(baseExpr))
            base = mbrExpr->baseExpression;

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

    Expr* SemanticsVisitor::CompleteOverloadCandidate(
        OverloadResolveContext&		context,
        OverloadCandidate&			candidate)
    {
        // special case for generic argument inference failure
        if (candidate.status == OverloadCandidate::Status::GenericArgumentInferenceFailed)
        {
            String callString = getCallSignatureString(context);
            getSink()->diagnose(
                context.loc,
                Diagnostics::genericArgumentInferenceFailed,
                callString);

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

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

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

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

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

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

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

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

            switch(candidate.flavor)
            {
            case OverloadCandidate::Flavor::Func:
                {
                    AppExprBase* callExpr = as<InvokeExpr>(context.originalExpr);
                    if(!callExpr)
                    {
                        callExpr = m_astBuilder->create<InvokeExpr>();
                        callExpr->loc = context.loc;

                        for(Index aa = 0; aa < context.argCount; ++aa)
                            callExpr->arguments.add(context.getArg(aa));
                    }


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

                    // A call may yield an l-value, and we should take a look at the candidate to be sure
                    if(auto subscriptDeclRef = candidate.item.declRef.as<SubscriptDecl>())
                    {
                        const auto& decl = subscriptDeclRef.getDecl();
                        if (decl->getMembersOfType<SetterDecl>().isNonEmpty() || decl->getMembersOfType<RefAccessorDecl>().isNonEmpty())
                        {
                            callExpr->type.isLeftValue = true;
                        }
                    }

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

                    return callExpr;
                }

                break;

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

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


    error:

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

        /// Does the given `declRef` represent an interface requirement?
    bool isInterfaceRequirement(DeclRef<Decl> const& declRef)
    {
        if(!declRef)
            return false;

        auto parent = declRef.getParent();
        if(parent.as<GenericDecl>())
            parent = parent.getParent();

        if(parent.as<InterfaceDecl>())
            return true;

        return false;
    }

        /// If `declRef` representations a specialization of a generic, returns the number of specialized generic arguments.
        /// Otherwise, returns zero.
        ///
    Int SemanticsVisitor::getSpecializedParamCount(DeclRef<Decl> const& declRef)
    {
        if(!declRef)
            return 0;

        // A specialization of a generic must point at the
        // "inner" declaration of a generic. That means that
        // the parent of the decl ref must be a generic.
        //
        auto parentGeneric = declRef.getParent().as<GenericDecl>();
        if(!parentGeneric)
            return 0;
        //
        // Furthermore, the declaration we are considering
        // must be the single "inner" declaration of the
        // parent generic (and not somthing like a generic
        // parameter).
        //
        if( parentGeneric.getDecl()->inner != declRef.getDecl())
            return 0;

        return CountParameters(parentGeneric).required;
    }

    int SemanticsVisitor::CompareLookupResultItems(
        LookupResultItem const& left,
        LookupResultItem const& right)
    {
        // It is possible for lookup to return both an interface requirement
        // and the concrete function that satisfies that requirement.
        // We always want to favor a concrete method over an interface
        // requirement it might override.
        //
        // TODO: This should turn into a more detailed check such that
        // a candidate for declaration A is always better than a candidate
        // for declaration B if A is an override of B. We can't
        // easily make that check right now because we aren't tracking
        // this kind of "is an override of ..." information on declarations
        // directly (it is only visible through the requirement witness
        // information for inheritance declarations).
        //
        bool leftIsInterfaceRequirement = isInterfaceRequirement(left.declRef);
        bool rightIsInterfaceRequirement = isInterfaceRequirement(right.declRef);
        if(leftIsInterfaceRequirement != rightIsInterfaceRequirement)
            return int(leftIsInterfaceRequirement) - int(rightIsInterfaceRequirement);

        // TODO: We should always have rules such that in a tie a declaration
        // A::m is better than B::m when all other factors are equal and
        // A inherits from B.

        // TODO: There are other cases like this we need to add in terms
        // of ranking/prioritizing overloads, around things like
        // "transparent" members, or when lookup proceeds from an "inner"
        // to an "outer" scope. In many cases the right way to proceed
        // could involve attaching a distance/cost/rank to things directly
        // as part of lookup, and in other cases it might be best handled
        // as a semantic check based on the actual declarations found.

        return 0;
    }

    int SemanticsVisitor::compareOverloadCandidateSpecificity(
        LookupResultItem const& left,
        LookupResultItem const& right)
    {
        // HACK: if both items refer to the same declaration,
        // then arbitrarily pick one.
        if(left.declRef.equals(right.declRef))
            return -1;

        // There is a very general rule that we would like to enforce
        // in principle:
        //
        // Given candidates A and B, if A being applicable to some
        // arguments implies that B is also applicable, but not vice versa,
        // then A is a more specific/specialized candidate than B.
        //
        // A number of conclusions follow from this general rule.
        // For example, a non-generic declaration will always be
        // more specific than a generic declaration that was specialized
        // to matching types:
        //
        //      int doThing(int a);
        //      T doThing<T>(T a);
        //
        // It is clear that if the non-generic `doThing` is applicable
        // to an argument `x`, then `doThing<int>` is also applicable to
        // `x`. However, knowing that the generic `doThing` was applicable
        // to some `y` doesn't tell us that the non-generic `doThing` can
        // be called on `y`, because `y` could have some type that can't
        // convert to `int`.
        //
        // Similarly, a generic declaration with a subset of the parameters
        // of another generic is always more specialized:
        //
        //      int doThing<T>(vector<T,3> value);
        //      int doThing<T, let N : int>(vector<T,N> value);
        //
        // Here we know that both overloads can apply to `float3`, but only
        // one can apply to `float4`, so the first overload is more
        // specialized/specific.
        //
        // As a final example, a generic which places more constraints
        // on its generic parameters is more specific, all other things
        // being equal:
        //
        //      int doThing<T : IFoo>( T value );
        //      int doThing<T>(T value);
        //
        // In this case we know that the first overload is applicable
        // to a strict subset of the types that the second overload can
        // apply to.
        //
        // The above rules represent the idealized principles we want
        // to implement, but actually implementing that full check here
        // could make overload resolution far more expensive.
        //
        // For now we are going to do something far simpler and hackier,
        // which is to say that a candidate with more generic parameters
        // is always preferred over one with fewer.
        //
        // TODO: We could extend this definition to account for constraints
        // on generic parameters in the count, which would handle the
        // need to prefer a more-constrained generic when possible.
        //
        // TODO: In the long run we should clearly replace this with
        // the more general "does A being applicable imply B being applicable"
        // test.
        //
        // TODO: The principle stated here doesn't take the actual
        // arguments or their types into account, and it might be that
        // in some cases disambiguation of which declaration should be
        // preferred will depend on knowing the actual arguments.
        //
        auto leftSpecCount = getSpecializedParamCount(left.declRef);
        auto rightSpecCount = getSpecializedParamCount(right.declRef);
        if(leftSpecCount != rightSpecCount)
            return int(leftSpecCount - rightSpecCount);

        return 0;
    }

    int SemanticsVisitor::CompareOverloadCandidates(
        OverloadCandidate*	left,
        OverloadCandidate*	right)
    {
        // If one candidate got further along in validation, pick it
        if (left->status != right->status)
            return int(right->status) - int(left->status);

        // If both candidates are applicable, then we need to compare
        // the costs of their type conversion sequences
        if(left->status == OverloadCandidate::Status::Applicable)
        {
            // If one candidate incurred less cost related to
            // implicit conversion of arguments to matching
            // parameter types, then we should prefer that
            // candidate.
            //
            // TODO: This eventually should be refined into
            // a test that checks conversion cost per-argument,
            // and only considers a candidate "better" if it
            // has lower cost for at least one argument, and
            // does not have higher cost for any.
            //
            if (left->conversionCostSum != right->conversionCostSum)
                return left->conversionCostSum - right->conversionCostSum;

            // If both candidates appear to be equally good when it
            // comes to the per-argument conversions required,
            // then we have two other categories of criteria we
            // can look at to disambiguate things:
            //
            // 1. We can look at how the lookup process found `left` and `right`
            //    do decide which is a better match based purely on how "far away"
            //    they are for lookup purposes. A canonincal example here would
            //    be if one declaration shadows or overrides the other.
            //
            // 2. We can look at parameter lists of `left` and `right`, their types, etc.
            //    do decide which is a better match based purely on structure.
            //    Canonical examples in this case would be preferring a non-generic
            //    candidate over a generic one, preferring a non-variadic candidate
            //    over a variadic one, and preferring a candidate with fewer
            //    default parameters over one with more.
            //
            // Deciding how to order/interleave these two categories of criteria
            // is an important design decision.
            //
            // For example, consider:
            //
            //      float f(float x);
            //
            //      struct S
            //      {
            //          int f<T>(T x);
            //
            //          float g(float y) { return f(y); }
            //      }
            //
            // In terms of structural/type matching, the global `f` is a more specialized
            // candidate at the call site, while in terms of lookup/lexical crieteria
            // the `S.f` declaration is better.
            //
            // For now we are considering lookup/overriding concerns first (so
            // we would bias in favor of selecting `S.f` in the above example), and then
            // structural/type concerns, but a more nuanced approach may be
            // required in the future to better match programmer intuition.
            //
            auto itemDiff = CompareLookupResultItems(left->item, right->item);
            if(itemDiff)
                return itemDiff;
            auto specificityDiff = compareOverloadCandidateSpecificity(left->item, right->item);
            if(specificityDiff)
                return specificityDiff;
        }

        return 0;
    }

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

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

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

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

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

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

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

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

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

    void SemanticsVisitor::AddFuncOverloadCandidate(
        LookupResultItem			item,
        DeclRef<CallableDecl>             funcDeclRef,
        OverloadResolveContext&		context)
    {
        auto funcDecl = funcDeclRef.getDecl();
        ensureDecl(funcDecl, DeclCheckState::CanUseFuncSignature);

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

        OverloadCandidate candidate;
        candidate.flavor = OverloadCandidate::Flavor::Func;
        candidate.item = item;
        candidate.resultType = getResultType(m_astBuilder, funcDeclRef);

        AddOverloadCandidate(context, candidate);
    }

    void SemanticsVisitor::AddFuncOverloadCandidate(
        FuncType*        funcType,
        OverloadResolveContext& context)
    {
        SLANG_UNUSED(funcType);
        getSink()->diagnose(context.loc, Diagnostics::unimplemented, "call on expression of function type");
    }

    void SemanticsVisitor::AddCtorOverloadCandidate(
        LookupResultItem            typeItem,
        Type*                type,
        DeclRef<ConstructorDecl>    ctorDeclRef,
        OverloadResolveContext&     context,
        Type*                resultType)
    {
        SLANG_UNUSED(type)

        ensureDecl(ctorDeclRef, DeclCheckState::CanUseFuncSignature);

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

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

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

        AddOverloadCandidate(context, candidate);
    }

    DeclRef<Decl> SemanticsVisitor::SpecializeGenericForOverload(
        DeclRef<GenericDecl>    genericDeclRef,
        OverloadResolveContext& context)
    {
        ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric);

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

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

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

            Index argCount = context.getArgCount();
            Index paramCount = params.getCount();

            // If there are too many arguments, we cannot possibly have a match.
            //
            // Note that if there are *too few* arguments, we might still have
            // a match, because the other arguments might have default values
            // that can be used.
            //
            if (argCount > paramCount)
            {
                return DeclRef<Decl>(nullptr, nullptr);
            }

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

                TryUnifyTypes(constraints, context.getArgTypeForInference(aa, this), getType(m_astBuilder, params[aa]));
            }
        }
        else
        {
            // TODO(tfoley): any other cases needed here?
            return DeclRef<Decl>(nullptr, nullptr);
        }

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

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

    void SemanticsVisitor::AddTypeOverloadCandidates(
        Type*            type,
        OverloadResolveContext&	context)
    {
        // The code being checked is trying to apply `type` like a function.
        // Semantically, the operations `T(args...)` is equivalent to
        // `T.__init(args...)` if we had a surface syntax that supported
        // looking up `__init` declarations by that name.
        //
        // Internally, all `__init` declarations are stored with the name
        // `$init`, to avoid potential conflicts if a user decided to name
        // a field/method `__init`.
        //
        // We will look up all the initializers on `type` by looking up
        // its members named `$init`, and then proceed to perform overload
        // resolution with what we find.
        //
        // TODO: One wrinkle here is single-argument constructor syntax.
        // An operation like `(T) oneArg` or `T(oneArg)` is currently
        // treated as a call expression, but we might want such cases
        // to go through the type coercion logic first/instead, because
        // by doing so we could weed out cases where a type is "constructed"
        // from a value of the same type. There is no need in Slang for
        // "copy constructors" but the stdlib currently has to define
        // some just to make code that does, e.g., `float(1.0f)` work.

        LookupResult initializers = lookUpMember(
            m_astBuilder,
            this,
            getName("$init"),
            type);

        AddOverloadCandidates(initializers, context);
    }

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

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

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

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

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

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

    void SemanticsVisitor::AddOverloadCandidates(
        LookupResult const&     result,
        OverloadResolveContext&	context)
    {
        if(result.isOverloaded())
        {
            for(auto item : result.items)
            {
                AddDeclRefOverloadCandidates(item, context);
            }
        }
        else
        {
            AddDeclRefOverloadCandidates(result.item, context);
        }
    }

    void SemanticsVisitor::AddOverloadCandidates(
        Expr*            funcExpr,
        OverloadResolveContext& context)
    {
        // A call of the form `(<something>)(<args>)` should be
        // resolved as if the user wrote `<something>(<args>)`,
        // so that we avoid introducing intermediate expressions
        // of function type in cases where they are not needed.
        //
        while(auto parenExpr = as<ParenExpr>(funcExpr))
        {
            funcExpr = parenExpr->base;
        }

        auto funcExprType = funcExpr->type;

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

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

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

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

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

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

        for (auto& arg : expr->arguments)
        {
            arg = maybeOpenExistential(arg);
        }

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

        context.argCount = expr->arguments.getCount();
        context.args = expr->arguments.getBuffer();
        context.loc = expr->loc;

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

        // TODO: We should have a special case here where an `InvokeExpr`
        // with a single argument where the base/func expression names
        // a type should always be treated as an explicit type coercion
        // (and hence bottleneck through `coerce()`) instead of just
        // as a constructor call.
        //
        // Such a special-case would help us handle cases of identity
        // casts (casting an expression to the type it already has),
        // without needing dummy initializer/constructor declarations.
        //
        // Handling that special casing here (rather than in, say,
        // `visitTypeCastExpr`) would allow us to continue to ensure
        // that `(T) expr` and `T(expr)` continue to be semantically
        // equivalent in (almost) all cases.

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

        if (context.bestCandidates.getCount() > 0)
        {
            // Things were ambiguous.

            // It might be that things were only ambiguous because
            // one of the argument expressions had an error, and
            // so a bunch of candidates could match at that position.
            //
            // If any argument was an error, we skip out on printing
            // another message, to avoid cascading errors.
            for (auto arg : expr->arguments)
            {
                if (IsErrorExpr(arg))
                {
                    return CreateErrorExpr(expr);
                }
            }

            Name* funcName = nullptr;
            {
                Expr* baseExpr = funcExpr;

                if(auto baseGenericApp = as<GenericAppExpr>(baseExpr))
                    baseExpr = baseGenericApp->functionExpr;

                if (auto baseVar = as<VarExpr>(baseExpr))
                    funcName = baseVar->name;
                else if(auto baseMemberRef = as<MemberExpr>(baseExpr))
                    funcName = baseMemberRef->name;
                else if(auto baseOverloaded = as<OverloadedExpr>(baseExpr))
                    funcName = baseOverloaded->name;
            }

            String argsList = getCallSignatureString(context);

            if (context.bestCandidates[0].status != OverloadCandidate::Status::Applicable)
            {
                // There were multiple equally-good candidates, but none actually usable.
                // We will construct a diagnostic message to help out.

                if (funcName)
                {
                    getSink()->diagnose(expr, Diagnostics::noApplicableOverloadForNameWithArgs, funcName, argsList);
                }
                else
                {
                    getSink()->diagnose(expr, Diagnostics::noApplicableWithArgs, argsList);
                }
            }
            else
            {
                // There were multiple applicable candidates, so we need to report them.

                if (funcName)
                {
                    getSink()->diagnose(expr, Diagnostics::ambiguousOverloadForNameWithArgs, funcName, argsList);
                }
                else
                {
                    getSink()->diagnose(expr, Diagnostics::ambiguousOverloadWithArgs, argsList);
                }
            }

            {
                Index candidateCount = context.bestCandidates.getCount();
                Index maxCandidatesToPrint = 10; // don't show too many candidates at once...
                Index candidateIndex = 0;
                for (auto candidate : context.bestCandidates)
                {
                    String declString = ASTPrinter::getDeclSignatureString(candidate.item, m_astBuilder);

//                        declString = declString + "[" + String(candidate.conversionCostSum) + "]";

#if 0
                    // Debugging: ensure that we don't consider multiple declarations of the same operation
                    if (auto decl = as<CallableDecl>(candidate.item.declRef.decl))
                    {
                        char buffer[1024];
                        sprintf_s(buffer, sizeof(buffer), "[this:%p, primary:%p, next:%p]",
                            decl,
                            decl->primaryDecl,
                            decl->nextDecl);
                        declString.append(buffer);
                    }
#endif

                    getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString);

                    candidateIndex++;
                    if (candidateIndex == maxCandidatesToPrint)
                        break;
                }
                if (candidateIndex != candidateCount)
                {
                    getSink()->diagnose(expr, Diagnostics::moreOverloadCandidates, candidateCount - candidateIndex);
                }
            }

            return CreateErrorExpr(expr);
        }
        else if (context.bestCandidate)
        {
            // There was one best candidate, even if it might not have been
            // applicable in the end.
            // We will report errors for this one candidate, then, to give
            // the user the most help we can.
            if (shouldAddToCache)
                typeCheckingCache->resolvedOperatorOverloadCache[key] = *context.bestCandidate;
            return CompleteOverloadCandidate(context, *context.bestCandidate);
        }
        else
        {
            // Nothing at all was found that we could even consider invoking
            getSink()->diagnose(expr->functionExpr, Diagnostics::expectedFunction, funcExprType);
            expr->type = QualType(m_astBuilder->getErrorType());
            return expr;
        }
    }

    void SemanticsVisitor::AddGenericOverloadCandidate(
        LookupResultItem		baseItem,
        OverloadResolveContext&	context)
    {
        if (auto genericDeclRef = baseItem.declRef.as<GenericDecl>())
        {
            ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric);

            OverloadCandidate candidate;
            candidate.flavor = OverloadCandidate::Flavor::Generic;
            candidate.item = baseItem;
            candidate.resultType = nullptr;

            AddOverloadCandidate(context, candidate);
        }
    }

    void SemanticsVisitor::AddGenericOverloadCandidates(
        Expr*	baseExpr,
        OverloadResolveContext&			context)
    {
        if(auto baseDeclRefExpr = as<DeclRefExpr>(baseExpr))
        {
            auto declRef = baseDeclRefExpr->declRef;
            AddGenericOverloadCandidate(LookupResultItem(declRef), context);
        }
        else if (auto overloadedExpr = as<OverloadedExpr>(baseExpr))
        {
            // We are referring to a bunch of declarations, each of which might be generic
            LookupResult result;
            for (auto item : overloadedExpr->lookupResult2.items)
            {
                AddGenericOverloadCandidate(item, context);
            }
        }
        else
        {
            // any other cases?
        }
    }

    Expr* SemanticsExprVisitor::visitGenericAppExpr(GenericAppExpr* genericAppExpr)
    {
        // Start by checking the base expression and arguments.
        auto& baseExpr = genericAppExpr->functionExpr;
        baseExpr = CheckTerm(baseExpr);
        auto& args = genericAppExpr->arguments;
        for (auto& arg : args)
        {
            arg = CheckTerm(arg);
        }

        return checkGenericAppWithCheckedArgs(genericAppExpr);
    }

        /// Check a generic application where the operands have already been checked.
    Expr* SemanticsVisitor::checkGenericAppWithCheckedArgs(GenericAppExpr* genericAppExpr)
    {
        // We are applying a generic to arguments, but there might be multiple generic
        // declarations with the same name, so this becomes a specialized case of
        // overload resolution.

        auto& baseExpr = genericAppExpr->functionExpr;
        auto& args = genericAppExpr->arguments;

        // If there was an error in the base expression,  or in any of
        // the arguments, then just bail.
        if (IsErrorExpr(baseExpr))
        {
            return CreateErrorExpr(genericAppExpr);
        }
        for (auto argExpr : args)
        {
            if (IsErrorExpr(argExpr))
            {
                return CreateErrorExpr(genericAppExpr);
            }
        }

        // Otherwise, let's start looking at how to find an overload...

        OverloadResolveContext context;
        context.originalExpr = genericAppExpr;
        context.funcLoc = baseExpr->loc;
        context.argCount = args.getCount();
        context.args = args.getBuffer();
        context.loc = genericAppExpr->loc;

        context.baseExpr = GetBaseExpr(baseExpr);

        AddGenericOverloadCandidates(baseExpr, context);

        if (context.bestCandidates.getCount() > 0)
        {
            // Things were ambiguous.
            if (context.bestCandidates[0].status != OverloadCandidate::Status::Applicable)
            {
                // There were multiple equally-good candidates, but none actually usable.
                // We will construct a diagnostic message to help out.

                // TODO(tfoley): print a reasonable message here...

                getSink()->diagnose(genericAppExpr, Diagnostics::unimplemented, "no applicable generic");

                return CreateErrorExpr(genericAppExpr);
            }
            else
            {
                // There were multiple viable candidates, but that isn't an error: we just need
                // to complete all of them and create an overloaded expression as a result.

                auto overloadedExpr = m_astBuilder->create<OverloadedExpr2>();
                overloadedExpr->base = context.baseExpr;
                for (auto candidate : context.bestCandidates)
                {
                    auto candidateExpr = CompleteOverloadCandidate(context, candidate);
                    overloadedExpr->candidiateExprs.add(candidateExpr);
                }
                return overloadedExpr;
            }
        }
        else if (context.bestCandidate)
        {
            // There was one best candidate, even if it might not have been
            // applicable in the end.
            // We will report errors for this one candidate, then, to give
            // the user the most help we can.
            return CompleteOverloadCandidate(context, *context.bestCandidate);
        }
        else
        {
            // Nothing at all was found that we could even consider invoking
            getSink()->diagnose(genericAppExpr, Diagnostics::expectedAGeneric, baseExpr->type);
            return CreateErrorExpr(genericAppExpr);
        }
    }

}
back to top