// 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 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 genericRef) { ParamCounts counts = { 0, 0 }; for (auto m : genericRef.getDecl()->members) { if (auto typeParam = as(m)) { counts.allowed++; if (!typeParam->initType.Ptr()) { counts.required++; } } else if (auto valParam = as(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())); break; case OverloadCandidate::Flavor::Generic: paramCounts = CountParameters(candidate.item.declRef.as()); 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(expr)) { if(decl->hasModifier()) 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(expr)) { if(decl->hasModifier()) 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(); // The basic idea here is that we need to check that the // arguments to a generic application (e.g., `F`) // 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(); 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()) { // 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()) { // 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(); } 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> params; switch (candidate.flavor) { case OverloadCandidate::Flavor::Func: params = getParameters(candidate.item.declRef.as()).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()) return true; if(decl->hasModifier()) return false; if(as(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(); 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(); 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(candidate.subst); SLANG_ASSERT(subst); subst->genericDecl = genericDeclRef.getDecl(); subst->outer = genericDeclRef.substitutions.substitutions; for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType() ) { auto subset = genericDeclRef.substitutions; subset.substitutions = subst; DeclRef 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(baseExpr); if (!baseDeclRefExpr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration"); return CreateErrorExpr(originalExpr); } auto baseGenericRef = baseDeclRefExpr->declRef.as(); 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 innerDeclRef(getInner(baseGenericRef), subst); Expr* base = nullptr; if (auto mbrExpr = as(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(context.originalExpr); if(!callExpr) { callExpr = m_astBuilder->create(); 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()) { const auto& decl = subscriptDeclRef.getDecl(); if (decl->getMembersOfType().isNonEmpty() || decl->getMembersOfType().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(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 const& declRef) { if(!declRef) return false; auto parent = declRef.getParent(); if(parent.as()) parent = parent.getParent(); if(parent.as()) 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 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(); 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 a); // // It is clear that if the non-generic `doThing` is applicable // to an argument `x`, then `doThing` 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(vector value); // int doThing(vector 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 value ); // int doThing(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 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 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 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 SemanticsVisitor::SpecializeGenericForOverload( DeclRef 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 unspecializedInnerRef = DeclRef(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()) { 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(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` and `vector` // // 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`) // // 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(nullptr, nullptr); } auto constraintSubst = TrySolveConstraintSystem(&constraints, genericDeclRef); if (!constraintSubst) { // constraint solving failed return DeclRef(nullptr, nullptr); } // We can now construct a reference to the inner declaration using // the solution to our constraints. return DeclRef(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()) { AddFuncOverloadCandidate(item, funcDeclRef, context); } else if (auto aggTypeDeclRef = item.declRef.as()) { auto type = DeclRefType::create(m_astBuilder, aggTypeDeclRef); AddTypeOverloadCandidates(type, context); } else if (auto genericDeclRef = item.declRef.as()) { // Try to infer generic arguments, based on the context DeclRef 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() ) { auto type = getNamedType(m_astBuilder, typeDefDeclRef); AddTypeOverloadCandidates(type, context); } else if( auto genericTypeParamDeclRef = item.declRef.as() ) { 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 `()()` should be // resolved as if the user wrote `()`, // so that we avoid introducing intermediate expressions // of function type in cases where they are not needed. // while(auto parenExpr = as(funcExpr)) { funcExpr = parenExpr->base; } auto funcExprType = funcExpr->type; if (auto declRefExpr = as(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(funcExprType)) { // TODO(tfoley): deprecate this path... AddFuncOverloadCandidate(funcType, context); } else if (auto overloadedExpr = as(funcExpr)) { AddOverloadCandidates(overloadedExpr->lookupResult2, context); } else if (auto overloadedExpr2 = as(funcExpr)) { for (auto item : overloadedExpr2->candidiateExprs) { AddOverloadCandidates(item, context); } } else if (auto typeType = as(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(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(funcExpr)) { context.baseExpr = funcMemberExpr->baseExpression; } else if (auto funcOverloadExpr = as(funcExpr)) { context.baseExpr = funcOverloadExpr->base; } else if (auto funcOverloadExpr2 = as(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(baseExpr)) baseExpr = baseGenericApp->functionExpr; if (auto baseVar = as(baseExpr)) funcName = baseVar->name; else if(auto baseMemberRef = as(baseExpr)) funcName = baseMemberRef->name; else if(auto baseOverloaded = as(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(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()) { 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(baseExpr)) { auto declRef = baseDeclRefExpr->declRef; AddGenericOverloadCandidate(LookupResultItem(declRef), context); } else if (auto overloadedExpr = as(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(); 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); } } }