diff options
| author | Theresa Foley <10618364+tangent-vector@users.noreply.github.com> | 2022-09-20 12:37:33 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-20 12:37:33 -0700 |
| commit | 5ac7ba2c6d3405f1a59f4350c753ec990af8f6dc (patch) | |
| tree | 13aba4c8e57cd5cbe6e3859bea130a8091c0a13a /source | |
| parent | 8e44968be297c0fa0ab00510a5e5922630d8c401 (diff) | |
Support partial inference of generic arguments (#2404)
A commonly requested feature is to be able to supply only some
of the arguments to a generic explicitly, while allowing the rest
to be inferred. A common example is a function that performs some
kind of conversion:
To convert<To, From>( From fromValue ) { .... }
A user would like to be able to call this operation like:
int i = convert<int>( 1.0f );
but the current Slang type checker requires all or none of the generic
arguments be supplied. Supplying all of the arguments is tedious:
int i = convert<int, float>( 1.0f );
In this case, the `float` type argument is redundant and could be
inferred from context. However, if the user tries to omit the generic
argument list:
int i = convert( 1.0f );
The current type-checker cannot infer the `int` type argument (even if
one might claim it *should* infer based on the desired result type).
This change adds support for the `convert<int>(...)` case, by allowing
a generic to be applied to a prefix of its explicit arguments, and then
inferring the remaining arguments from contextual information when that
"partially applied" generic is applied to value-level arguments.
Most of the changes are just plumbing: adding the notion of a partially
applied generic and then supporting them during overload resolution.
A single test case is included that covers the `convert`-style use case.
It is likely that more testing is needed to cover failure modes of this
feature.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-ast-base.h | 8 | ||||
| -rw-r--r-- | source/slang/slang-ast-expr.h | 17 | ||||
| -rw-r--r-- | source/slang/slang-ast-type.cpp | 39 | ||||
| -rw-r--r-- | source/slang/slang-ast-val.cpp | 43 | ||||
| -rw-r--r-- | source/slang/slang-check-constraint.cpp | 77 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 42 | ||||
| -rw-r--r-- | source/slang/slang-check-overload.cpp | 324 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 6 |
9 files changed, 407 insertions, 151 deletions
diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h index dd08fece2..dea02afbb 100644 --- a/source/slang/slang-ast-base.h +++ b/source/slang/slang-ast-base.h @@ -152,6 +152,14 @@ class Val : public NodeBase SLANG_FORCE_INLINE StringBuilder& operator<<(StringBuilder& io, Val* val) { SLANG_ASSERT(val); val->toText(io); return io; } + /// Given a `value` that refers to a `param` of some generic, attempt to apply + /// the `subst` to it and produce a new `Val` as a result. + /// + /// If the `subst` does not include anything to replace `value`, then this function + /// returns null. + /// +Val* maybeSubstituteGenericParam(Val* value, Decl* param, SubstitutionSet subst, int* ioDiff); + class Type; template <typename T> diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index 70390255f..1e4440042 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -481,9 +481,24 @@ class ModifiedTypeExpr : public Expr /// A type expression that rrepresents a pointer type, e.g. T* class PointerTypeExpr : public Expr { - SLANG_AST_CLASS(PointerTypeExpr) + SLANG_AST_CLASS(PointerTypeExpr); TypeExp base; }; + /// An expression that applies a generic to arguments for some, + /// but not all, of its explicit parameters. + /// +class PartiallyAppliedGenericExpr : public Expr +{ + SLANG_AST_CLASS(PartiallyAppliedGenericExpr); + +public: + /// The generic being applied + DeclRef<GenericDecl> baseGenericDeclRef; + + /// A substitution that includes the generic arguments known so far + GenericSubstitution* substWithKnownGenericArgs = nullptr; +}; + } // namespace Slang diff --git a/source/slang/slang-ast-type.cpp b/source/slang/slang-ast-type.cpp index 077d6de0a..39b7a8e04 100644 --- a/source/slang/slang-ast-type.cpp +++ b/source/slang/slang-ast-type.cpp @@ -196,6 +196,8 @@ Type* DeclRefType::_createCanonicalTypeOverride() return this; } +Val* maybeSubstituteGenericParam(Val* paramVal, Decl* paramDecl, SubstitutionSet subst, int* ioDiff); + Val* DeclRefType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) { if (!subst) return this; @@ -204,41 +206,8 @@ Val* DeclRefType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSe // of a generic parameter, since that is what we might be substituting... if (auto genericTypeParamDecl = as<GenericTypeParamDecl>(declRef.getDecl())) { - // search for a substitution that might apply to us - for (auto s = subst.substitutions; s; s = s->outer) - { - auto genericSubst = as<GenericSubstitution>(s); - if (!genericSubst) - continue; - - // the generic decl associated with the substitution list must be - // the generic decl that declared this parameter - auto genericDecl = genericSubst->genericDecl; - if (genericDecl != genericTypeParamDecl->parentDecl) - continue; - - int index = 0; - for (auto m : genericDecl->members) - { - if (m == genericTypeParamDecl) - { - // We've found it, so return the corresponding specialization argument - (*ioDiff)++; - return genericSubst->getArgs()[index]; - } - else if (auto typeParam = as<GenericTypeParamDecl>(m)) - { - index++; - } - else if (auto valParam = as<GenericValueParamDecl>(m)) - { - index++; - } - else - { - } - } - } + if (auto result = maybeSubstituteGenericParam(this, genericTypeParamDecl, subst, ioDiff)) + return result; } int diff = 0; DeclRef<Decl> substDeclRef = declRef.substituteImpl(astBuilder, subst, &diff); diff --git a/source/slang/slang-ast-val.cpp b/source/slang/slang-ast-val.cpp index a70a79535..b5291509f 100644 --- a/source/slang/slang-ast-val.cpp +++ b/source/slang/slang-ast-val.cpp @@ -115,7 +115,7 @@ HashCode GenericParamIntVal::_getHashCodeOverride() return declRef.getHashCode() ^ HashCode(0xFFFF); } -Val* GenericParamIntVal::_substituteImplOverride(ASTBuilder* /* astBuilder */, SubstitutionSet subst, int* ioDiff) +Val* maybeSubstituteGenericParam(Val* paramVal, Decl* paramDecl, SubstitutionSet subst, int* ioDiff) { // search for a substitution that might apply to us for (auto s = subst.substitutions; s; s = s->outer) @@ -127,25 +127,45 @@ Val* GenericParamIntVal::_substituteImplOverride(ASTBuilder* /* astBuilder */, S // the generic decl associated with the substitution list must be // the generic decl that declared this parameter auto genericDecl = genSubst->genericDecl; - if (genericDecl != declRef.getDecl()->parentDecl) + if (genericDecl != paramDecl->parentDecl) continue; - int index = 0; + // In some cases, we construct a `DeclRef` to a `GenericDecl` + // (or a declaration under one) that only includes argument + // values for a prefix of the parameters of the generic. + // + // If we aren't careful, we could end up indexing into the + // argument list past the available range. + // + Count argCount = genSubst->getArgs().getCount(); + + Count argIndex = 0; for (auto m : genericDecl->members) { - if (m == declRef.getDecl()) + // If we have run out of arguments, then we can stop + // iterating over the parameters, because `this` + // parameter will not be replaced with anything by + // the substituion. + // + if (argIndex >= argCount) + { + return paramVal; + } + + + if (m == paramDecl) { // We've found it, so return the corresponding specialization argument (*ioDiff)++; - return genSubst->getArgs()[index]; + return genSubst->getArgs()[argIndex]; } else if (auto typeParam = as<GenericTypeParamDecl>(m)) { - index++; + argIndex++; } else if (auto valParam = as<GenericValueParamDecl>(m)) { - index++; + argIndex++; } else { @@ -154,6 +174,15 @@ Val* GenericParamIntVal::_substituteImplOverride(ASTBuilder* /* astBuilder */, S } // Nothing found: don't substitute. + return paramVal; + +} + +Val* GenericParamIntVal::_substituteImplOverride(ASTBuilder* /* astBuilder */, SubstitutionSet subst, int* ioDiff) +{ + if (auto result = maybeSubstituteGenericParam(this, declRef.getDecl(), subst, ioDiff)) + return result; + return this; } diff --git a/source/slang/slang-check-constraint.cpp b/source/slang/slang-check-constraint.cpp index 129d3ed0c..24cedd7d5 100644 --- a/source/slang/slang-check-constraint.cpp +++ b/source/slang/slang-check-constraint.cpp @@ -263,9 +263,10 @@ namespace Slang return nullptr; } - SubstitutionSet SemanticsVisitor::TrySolveConstraintSystem( - ConstraintSystem* system, - DeclRef<GenericDecl> genericDeclRef) + SubstitutionSet SemanticsVisitor::trySolveConstraintSystem( + ConstraintSystem* system, + DeclRef<GenericDecl> genericDeclRef, + GenericSubstitution* substWithKnownGenericArgs) { // For now the "solver" is going to be ridiculously simplistic. @@ -290,14 +291,60 @@ namespace Slang return SubstitutionSet(); } SubstitutionSet resultSubst = genericDeclRef.substitutions; - // We will loop over the generic parameters, and for - // each we will try to find a way to satisfy all - // the constraints for that parameter + + // Once have built up the full list of constraints we are trying to satisfy, + // we will attempt to solve for each parameter in a way that satisfies all + // the constraints that apply to that parameter. + // + // Note: this is a very limited kind of solver, in that it doesn't have a + // way to make use of constraints between two or more parameters. + // + // As we go, we will build up a list of argument values for a possible + // solution for how to assign the parameters in a way that satisfies all + // the constraints. + // List<Val*> args; + + // If the context is such that some of the arguments are already specified + // or known, we need to go ahead and use those arguments direclty (whether + // or not they are compatible with the constraints). + // + Count knownGenericArgCount = 0; + if (substWithKnownGenericArgs) + { + knownGenericArgCount = substWithKnownGenericArgs->getArgs().getCount(); + for (auto arg : substWithKnownGenericArgs->getArgs()) + { + args.add(arg); + } + } + + // We will then iterate over the explicit parameters of the generic + // and try to solve for each. + // + Count paramCounter = 0; for (auto m : getMembers(genericDeclRef)) { if (auto typeParam = m.as<GenericTypeParamDecl>()) { + // If the parameter is one where we already know + // the argument value to use, we don't bother with + // trying to solve for it, and treat any constraints + // on such a parameter as implicitly solved-for. + // + Index paramIndex = paramCounter++; + if (paramIndex < knownGenericArgCount) + { + for (auto& c : system->constraints) + { + if (c.decl != typeParam.getDecl()) + continue; + + c.satisfied = true; + } + continue; + } + Type* type = nullptr; for (auto& c : system->constraints) { @@ -334,6 +381,24 @@ namespace Slang } else if (auto valParam = m.as<GenericValueParamDecl>()) { + // If the parameter is one where we already know + // the argument value to use, we don't bother with + // trying to solve for it, and treat any constraints + // on such a parameter as implicitly solved-for. + // + Index paramIndex = paramCounter++; + if (paramIndex < knownGenericArgCount) + { + for (auto& c : system->constraints) + { + if (c.decl != typeParam.getDecl()) + continue; + + c.satisfied = true; + } + continue; + } + // TODO(tfoley): maybe support more than integers some day? // TODO(tfoley): figure out how this needs to interact with // compile-time integers that aren't just constants... diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 660e28802..a7cd768b1 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -5229,7 +5229,7 @@ namespace Slang if (!TryUnifyTypes(constraints, extDecl->targetType.Ptr(), type)) return DeclRef<ExtensionDecl>(); - auto constraintSubst = TrySolveConstraintSystem(&constraints, DeclRef<Decl>(extGenericDecl, nullptr).as<GenericDecl>()); + auto constraintSubst = trySolveConstraintSystem(&constraints, DeclRef<Decl>(extGenericDecl, nullptr).as<GenericDecl>()); if (!constraintSubst) { return DeclRef<ExtensionDecl>(); diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index ef500d4fa..b926907e2 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -180,6 +180,13 @@ namespace Slang }; Status status = Status::Unchecked; + typedef unsigned int Flags; + enum Flag : Flags + { + IsPartiallyAppliedGeneric = 1 << 0, + }; + Flags flags = 0; + // Reference to the declaration being applied LookupResultItem item; @@ -1366,9 +1373,10 @@ namespace Slang // // Returns a new substitution representing the values that // we solved for along the way. - SubstitutionSet TrySolveConstraintSystem( - ConstraintSystem* system, - DeclRef<GenericDecl> genericDeclRef); + SubstitutionSet trySolveConstraintSystem( + ConstraintSystem* system, + DeclRef<GenericDecl> genericDeclRef, + GenericSubstitution* substWithKnownGenericArgs = nullptr); // State related to overload resolution for a call @@ -1616,12 +1624,16 @@ namespace Slang ExtensionDecl* extDecl, Type* type); - // Take a generic declaration and try to specialize its parameters - // so that the resulting inner declaration can be applicable in - // a particular context... - DeclRef<Decl> SpecializeGenericForOverload( + // Take a generic declaration that is being applied + // in a context and attempt to infer any missing generic + // arguments to form a `DeclRef` to the inner declaration + // that could be applicable in the context of the given + // overloaded call. + // + DeclRef<Decl> inferGenericArguments( DeclRef<GenericDecl> genericDeclRef, - OverloadResolveContext& context); + OverloadResolveContext& context, + GenericSubstitution* substWithKnownGenericArgs); void AddTypeOverloadCandidates( Type* type, @@ -1652,6 +1664,19 @@ namespace Slang Expr* baseExpr, OverloadResolveContext& context); + // Add overload candidates based on use of `genericDeclRef` + // in an ordinary function-call context (that is, where it + // has been applied to arguments using `()` and not `<>`). + // + // If some or all of the generic arguments to `genericDeclRef` + // are known at the call site, they should be passed in via + // `substWithKnownGenericArgs`. + // + void addOverloadCandidatesForCallToGeneric( + LookupResultItem genericItem, + OverloadResolveContext& context, + GenericSubstitution* substWithKnownGenericArgs = nullptr); + /// Check a generic application where the operands have already been checked. Expr* checkGenericAppWithCheckedArgs(GenericAppExpr* genericAppExpr); @@ -1786,6 +1811,7 @@ namespace Slang CASE(ExtractExistentialValueExpr) CASE(OpenRefExpr) CASE(MakeOptionalExpr) + CASE(PartiallyAppliedGenericExpr) #undef CASE diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index 55263453d..57c9c1199 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -108,6 +108,13 @@ namespace Slang case OverloadCandidate::Flavor::Generic: paramCounts = CountParameters(candidate.item.declRef.as<GenericDecl>()); + + // A generic can be applied to any number of arguments less + // than or equal to the number of explicitly declared parameters. + // When a program provides fewer arguments than their are parameters, + // the rest will be inferred. + // + paramCounts.required = 0; break; case OverloadCandidate::Flavor::Expr: @@ -209,6 +216,7 @@ namespace Slang // appropriate forms. // auto genSubst = m_astBuilder->create<GenericSubstitution>(); + genSubst->genericDecl = genericDeclRef.getDecl(); candidate.subst = genSubst; auto& checkedArgs = (List<Val*>&)genSubst->getArgs(); @@ -228,39 +236,41 @@ namespace Slang { if (auto typeParamRef = memberRef.as<GenericTypeParamDecl>()) { + if (aa >= context.argCount) + { + // If we have run out of arguments, then we don't + // apply any more checks at this step. We will instead + // attempt to *infer* an argument at this position + // at a later stage. + // + candidate.flags |= OverloadCandidate::Flag::IsPartiallyAppliedGeneric; + break; + } + // We have a type parameter, and we expect to find // a type argument. // TypeExp typeArg; - if( aa >= context.argCount ) + + // Per the earlier check, we have at least one + // argument left, so we will 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) { - // 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; + typeArg = tryCoerceToProperType(TypeExp(arg)); } 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 - { - arg = ExpectATypeRepr(arg); - typeArg = CoerceToProperType(TypeExp(arg)); - } + arg = ExpectATypeRepr(arg); + typeArg = CoerceToProperType(TypeExp(arg)); } // If we failed to get a valid type (either because @@ -278,37 +288,39 @@ namespace Slang } else if (auto valParamRef = memberRef.as<GenericValueParamDecl>()) { + if (aa >= context.argCount) + { + // If we have run out of arguments, then we don't + // apply any more checks at this step. We will instead + // attempt to *infer* an argument at this position + // at a later stage. + // + candidate.flags |= OverloadCandidate::Flag::IsPartiallyAppliedGeneric; + break; + } + // The case for a generic value parameter is similar to that // for a generic type parameter. // Expr* arg = nullptr; - if( aa >= context.argCount ) + + // 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) { - // If there are no arguments left to consume, then - // we have a definite failure. - // - success = false; + ConversionCost cost = kConversionCost_None; + if (!canCoerce(getType(m_astBuilder, valParamRef), arg->type, arg, &cost)) + { + success = false; + } + candidate.conversionCostSum += cost; } 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); - } + arg = coerce(getType(m_astBuilder, valParamRef), arg); } // If we have an argument to work with, then we will @@ -481,12 +493,22 @@ namespace Slang if(candidate.flavor != OverloadCandidate::Flavor::Generic) return true; + // It is possible that the overload candidate was only partially + // applied (the number of arguments was not equal to the number + // of explicit parameters). In that case, we want to defer + // final checking of things like constraints until later, in + // case a subsequent pass of overload resolution (like applying + // an overloaded generic function to arguments) will give us + // the missing information to enable inference. + // + if(candidate.flags & OverloadCandidate::Flag::IsPartiallyAppliedGeneric) + 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); @@ -699,6 +721,20 @@ namespace Slang break; case OverloadCandidate::Flavor::Generic: + // We allow a generic to be applied to fewer arguments than its number + // of parameters, and defer the process of inferring the remaining + // arguments until later. + // + if(candidate.flags & OverloadCandidate::Flag::IsPartiallyAppliedGeneric) + { + auto expr = m_astBuilder->create<PartiallyAppliedGenericExpr>(); + expr->loc = context.loc; + + expr->baseGenericDeclRef = as<DeclRefExpr>(baseExpr)->declRef.as<GenericDecl>(); + expr->substWithKnownGenericArgs = (GenericSubstitution*)candidate.subst; + return expr; + } + return createGenericDeclRef( baseExpr, context.originalExpr, @@ -1135,30 +1171,75 @@ namespace Slang AddOverloadCandidate(context, candidate); } - DeclRef<Decl> SemanticsVisitor::SpecializeGenericForOverload( + DeclRef<Decl> SemanticsVisitor::inferGenericArguments( DeclRef<GenericDecl> genericDeclRef, - OverloadResolveContext& context) + OverloadResolveContext& context, + GenericSubstitution* substWithKnownGenericArgs) { + // We have been asked to infer zero or more arguments to + // `genericDeclRef`, in a context where it is being applied + // to value-level arguments in `context`. + // + // It is possible that the call site included one or more + // explicit arguments, in which case `substWithKnownGenericArgs` + // will have been filled in and contain those. Otherwise, + // that parameter will be null, and we are expected to + // infer all arguments. + + // The declaration of the generic must be checked up to a point + // where we can attempt to form specializations of it (which in + // practice means that the declarations of its parameters and + // their constraints must have been checked). + // ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric); + // Conceptually, we are going to be trying to infer any unspecified + // generic arguments by forming a system of constraints on those arguments + // and then attempting to solve the constraint system. + // + // While the constraint solver we have implemented today is not especially + // clever, we follow a flow that should in principle allow us to plug in + // something more clever down the line. + // 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. + // In order to perform matching between the types passed in at the + // call site represented by `context` and the parameters of the + // declaraiton being applied, we want to form a reference to + // the "inner" declaration of the generic (e.g., the `FuncitonDecl` + // under the `GenericDecl`). + // + // In the case where no explicit arguments are available, we will + // use any substitutions that were in place for referring to the + // generic itself. + // + Substitutions* substForInnerDecl = genericDeclRef.substitutions; + Count knownGenericArgCount = 0; + // + // In the case where we have explicit/known arguments, + // we will use those as our baseline substitutions. + // + if (substWithKnownGenericArgs) + { + substForInnerDecl = substWithKnownGenericArgs; + knownGenericArgCount = substWithKnownGenericArgs->getArgs().getCount(); + } + auto innerDecl = getInner(genericDeclRef); - DeclRef<Decl> unspecializedInnerRef = DeclRef<Decl>(innerDecl, genericDeclRef.substitutions); + DeclRef<Decl> partiallySpecializedInnerRef = DeclRef<Decl>( + innerDecl, + substForInnerDecl); // 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>()) + if (auto funcDeclRef = partiallySpecializedInnerRef.as<CallableDecl>()) { auto params = getParameters(funcDeclRef).toArray(); - Index argCount = context.getArgCount(); - Index paramCount = params.getCount(); + Index valueArgCount = context.getArgCount(); + Index valueParamCount = params.getCount(); // If there are too many arguments, we cannot possibly have a match. // @@ -1166,12 +1247,16 @@ namespace Slang // a match, because the other arguments might have default values // that can be used. // - if (argCount > paramCount) + if (valueArgCount > valueParamCount) { return DeclRef<Decl>(nullptr, nullptr); } - for (Index aa = 0; aa < argCount; ++aa) + // If any of the arguments were specified explicitly (and are thus known), + // we do not want to take them into account during the unification and + // constraint generation step. + // + for (Index aa = 0; aa < valueArgCount; ++aa) { // The question here is whether failure to "unify" an argument // and parameter should lead to immediate failure. @@ -1191,7 +1276,10 @@ namespace Slang // 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])); + TryUnifyTypes( + constraints, + context.getArgTypeForInference(aa, this), + getType(m_astBuilder, params[aa])); } } else @@ -1200,15 +1288,38 @@ namespace Slang return DeclRef<Decl>(nullptr, nullptr); } - auto constraintSubst = TrySolveConstraintSystem(&constraints, genericDeclRef); + // Once we have added all the appropriate constraints to the system, we + // will try to solve for a set of arguments to the generic that satisfy + // those constraints. + // + // Note that this step *also* attempts to infer arguments for all the + // implicit parameters of a generic. Notably, this means inferring + // witnesses for interface conformance constraints. + // + // TODO(tfoley): We probably need to pass along the explicit arguments here, + // so that the solver knows to accept those arguments as-is. + // + auto constraintSubst = trySolveConstraintSystem( + &constraints, genericDeclRef, substWithKnownGenericArgs); if (!constraintSubst) { - // constraint solving failed + // In this case, the solver failed to find a solution to the constraint + // system, and we will signal that failure up to the client that called + // this operation. + // + // TODO: We really ought to be passing up some kind of representation + // of the failure, so that constraint-related issues can be reported to + // the user. This could either be a return path here (returning some + // diagnostics), or this code could have a "just trying" vs. "actually + // do things" distinction like some other steps. + // return DeclRef<Decl>(nullptr, nullptr); } - // We can now construct a reference to the inner declaration using - // the solution to our constraints. + // If we found a solution (that is, a set of argument values that satisfy + // all the constraints), we can construct a reference to the inner + // declaration that applies the generic to those arguments. + // return DeclRef<Decl>(innerDecl, constraintSubst); } @@ -1249,9 +1360,50 @@ namespace Slang AddOverloadCandidates(initializers, context); } + void SemanticsVisitor::addOverloadCandidatesForCallToGeneric( + LookupResultItem genericItem, + OverloadResolveContext& context, + GenericSubstitution* substWithKnownGenericArgs) + { + auto genericDeclRef = genericItem.declRef.as<GenericDecl>(); + SLANG_ASSERT(genericDeclRef); + + if (substWithKnownGenericArgs) + { + substWithKnownGenericArgs = substWithKnownGenericArgs; + } + + // Try to infer generic arguments, based on the context + DeclRef<Decl> innerRef = inferGenericArguments(genericDeclRef, context, substWithKnownGenericArgs); + + if (innerRef) + { + // If inference works, then we've now got a + // specialized declaration reference we can apply. + + LookupResultItem innerItem; + innerItem.breadcrumbs = genericItem.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 = genericItem; + candidate.flavor = OverloadCandidate::Flavor::UnspecializedGeneric; + candidate.status = OverloadCandidate::Status::GenericArgumentInferenceFailed; + + AddOverloadCandidateInner(context, candidate); + } + } + void SemanticsVisitor::AddDeclRefOverloadCandidates( - LookupResultItem item, - OverloadResolveContext& context) + LookupResultItem item, + OverloadResolveContext& context) { auto declRef = item.declRef; @@ -1266,32 +1418,7 @@ namespace Slang } 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); - } + addOverloadCandidatesForCallToGeneric(LookupResultItem(genericDeclRef), context); } else if( auto typeDefDeclRef = item.declRef.as<TypeDefDecl>() ) { @@ -1365,6 +1492,17 @@ namespace Slang AddOverloadCandidates(item, context); } } + else if (auto partiallyAppliedGenericExpr = as<PartiallyAppliedGenericExpr>(funcExpr)) + { + // A partially-applied generic is allowed as an overload candidate, + // and carries along an (incomplete) substitution that can be used + // to carry the arguments known so far. + // + addOverloadCandidatesForCallToGeneric( + LookupResultItem(partiallyAppliedGenericExpr->baseGenericDeclRef), + context, + partiallyAppliedGenericExpr->substWithKnownGenericArgs); + } else if (auto typeType = as<TypeType>(funcExprType)) { // If none of the above cases matched, but we are diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index c02efc971..753e0d6e9 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -3069,6 +3069,12 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> UNREACHABLE_RETURN(LoweredValInfo()); } + LoweredValInfo visitPartiallyAppliedGenericExpr(PartiallyAppliedGenericExpr* /*expr*/) + { + SLANG_UNEXPECTED("partially applied generics should not occur in checked AST"); + UNREACHABLE_RETURN(LoweredValInfo()); + } + LoweredValInfo visitIndexExpr(IndexExpr* expr) { auto type = lowerType(context, expr->type); |
