summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTheresa Foley <10618364+tangent-vector@users.noreply.github.com>2022-09-20 12:37:33 -0700
committerGitHub <noreply@github.com>2022-09-20 12:37:33 -0700
commit5ac7ba2c6d3405f1a59f4350c753ec990af8f6dc (patch)
tree13aba4c8e57cd5cbe6e3859bea130a8091c0a13a /source
parent8e44968be297c0fa0ab00510a5e5922630d8c401 (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.h8
-rw-r--r--source/slang/slang-ast-expr.h17
-rw-r--r--source/slang/slang-ast-type.cpp39
-rw-r--r--source/slang/slang-ast-val.cpp43
-rw-r--r--source/slang/slang-check-constraint.cpp77
-rw-r--r--source/slang/slang-check-decl.cpp2
-rw-r--r--source/slang/slang-check-impl.h42
-rw-r--r--source/slang/slang-check-overload.cpp324
-rw-r--r--source/slang/slang-lower-to-ir.cpp6
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);