summaryrefslogtreecommitdiff
path: root/source/slang/slang-check-decl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/slang-check-decl.cpp')
-rw-r--r--source/slang/slang-check-decl.cpp434
1 files changed, 276 insertions, 158 deletions
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index c2b356eff..ad3f506a6 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -1407,7 +1407,7 @@ namespace Slang
bool SemanticsVisitor::findWitnessForInterfaceRequirement(
ConformanceCheckingContext* context,
- DeclRef<AggTypeDeclBase> typeDeclRef,
+ Type* type,
InheritanceDecl* inheritanceDecl,
DeclRef<InterfaceDecl> interfaceDeclRef,
DeclRef<Decl> requiredMemberDeclRef,
@@ -1447,7 +1447,7 @@ namespace Slang
RefPtr<WitnessTable> satisfyingWitnessTable = checkConformanceToType(
context,
- typeDeclRef,
+ type,
requiredInheritanceDeclRef.getDecl(),
getBaseType(requiredInheritanceDeclRef));
@@ -1464,42 +1464,34 @@ namespace Slang
// since only same-name members will be able to
// satisfy the requirement.
//
- // TODO: this won't work right now for members that
- // don't have names, which right now includes
- // initializers/constructors.
Name* name = requiredMemberDeclRef.GetName();
- // We are basically looking up members of the
- // given type, but we need to be a bit careful.
- // We *cannot* perfom lookup "through" inheritance
- // declarations for this or other interfaces,
- // since that would let us satisfy a requirement
- // with itself.
- //
- // There's also an interesting question of whether
- // we can/should support innterface requirements
- // being satisfied via `__transparent` members.
- // This seems like a "clever" idea rather than
- // a useful one, and IR generation would
- // need to construct real IR to trampoline over
- // to the implementation.
- //
- // The final case that can't be reduced to just
- // "a directly declared member with the same name"
- // is the case where the type inherits a member
- // that can satisfy the requirement from a base type.
- // We are ignoring implementation inheritance for
- // now, so we won't worry about this.
-
- // Make sure that by-name lookup is possible.
- buildMemberDictionary(typeDeclRef.getDecl());
- auto lookupResult = lookUpLocal(getSession(), this, name, typeDeclRef);
-
- if (!lookupResult.isValid())
- {
- getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef);
- return false;
- }
+ // We start by looking up members of the same
+ // name, on the type that is claiming to conform.
+ //
+ // This lookup step could include members that
+ // we might not actually want to consider:
+ //
+ // * Lookup through a type `Foo` where `Foo : IBar`
+ // will be able to find members of `IBar`, which
+ // somewhat obviously shouldn't apply when
+ // determining if `Foo` satisfies the requirements
+ // of `IBar`.
+ //
+ // * Lookup in the presence of `__transparent` members
+ // may produce references to declarations on a *field*
+ // of the type rather than the type. Conformance through
+ // transparent members could be supported in theory,
+ // but would require synthesizing proxy/forwarding
+ // implementations in the type itself.
+ //
+ // We will punt on the second issue for now (since
+ // transparent members aren't currently exposed as
+ // a general-purpose feature for users), and rely
+ // on subsequent checking in this function to
+ // rule out inherited abstract members.
+ //
+ auto lookupResult = lookUpMember(getSession(), this, name, type);
// Iterate over the members and look for one that matches
// the expected signature for the requirement.
@@ -1516,13 +1508,13 @@ namespace Slang
// of "candidates" for satisfaction of the requirement,
// and if nothing is found we print the candidates
- getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef);
+ getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, type, requiredMemberDeclRef);
return false;
}
RefPtr<WitnessTable> SemanticsVisitor::checkInterfaceConformance(
ConformanceCheckingContext* context,
- DeclRef<AggTypeDeclBase> typeDeclRef,
+ Type* type,
InheritanceDecl* inheritanceDecl,
DeclRef<InterfaceDecl> interfaceDeclRef)
{
@@ -1564,7 +1556,7 @@ namespace Slang
{
auto requirementSatisfied = findWitnessForInterfaceRequirement(
context,
- typeDeclRef,
+ type,
inheritanceDecl,
interfaceDeclRef,
requiredMemberDeclRef,
@@ -1615,7 +1607,7 @@ namespace Slang
{
auto requirementSatisfied = findWitnessForInterfaceRequirement(
context,
- typeDeclRef,
+ type,
inheritanceDecl,
interfaceDeclRef,
requiredInheritanceDeclRef,
@@ -1638,7 +1630,7 @@ namespace Slang
RefPtr<WitnessTable> SemanticsVisitor::checkConformanceToType(
ConformanceCheckingContext* context,
- DeclRef<AggTypeDeclBase> typeDeclRef,
+ Type* type,
InheritanceDecl* inheritanceDecl,
Type* baseType)
{
@@ -1652,7 +1644,7 @@ namespace Slang
// required by that interface.
return checkInterfaceConformance(
context,
- typeDeclRef,
+ type,
inheritanceDecl,
baseInterfaceDeclRef);
}
@@ -1663,41 +1655,43 @@ namespace Slang
}
bool SemanticsVisitor::checkConformance(
- DeclRef<AggTypeDeclBase> declRef,
+ Type* type,
InheritanceDecl* inheritanceDecl)
{
- declRef = createDefaultSubstitutionsIfNeeded(getSession(), declRef).as<AggTypeDeclBase>();
-
- // Don't check conformances for abstract types that
- // are being used to express *required* conformances.
- if (auto assocTypeDeclRef = declRef.as<AssocTypeDecl>())
- {
- // An associated type declaration represents a requirement
- // in an outer interface declaration, and its members
- // (type constraints) represent additional requirements.
- return true;
- }
- else if (auto interfaceDeclRef = declRef.as<InterfaceDecl>())
+ if( auto declRefType = as<DeclRefType>(type) )
{
- // HACK: Our semantics as they stand today are that an
- // `extension` of an interface that adds a new inheritance
- // clause acts *as if* that inheritnace clause had been
- // attached to the original `interface` decl: that is,
- // it adds additional requirements.
- //
- // This is *not* a reasonable semantic to keep long-term,
- // but it is required for some of our current example
- // code to work.
- return true;
- }
+ auto declRef = declRefType->declRef;
+ // Don't check conformances for abstract types that
+ // are being used to express *required* conformances.
+ if (auto assocTypeDeclRef = declRef.as<AssocTypeDecl>())
+ {
+ // An associated type declaration represents a requirement
+ // in an outer interface declaration, and its members
+ // (type constraints) represent additional requirements.
+ return true;
+ }
+ else if (auto interfaceDeclRef = declRef.as<InterfaceDecl>())
+ {
+ // HACK: Our semantics as they stand today are that an
+ // `extension` of an interface that adds a new inheritance
+ // clause acts *as if* that inheritnace clause had been
+ // attached to the original `interface` decl: that is,
+ // it adds additional requirements.
+ //
+ // This is *not* a reasonable semantic to keep long-term,
+ // but it is required for some of our current example
+ // code to work.
+ return true;
+ }
+ }
// Look at the type being inherited from, and validate
// appropriately.
auto baseType = inheritanceDecl->base.type;
ConformanceCheckingContext context;
- RefPtr<WitnessTable> witnessTable = checkConformanceToType(&context, declRef, inheritanceDecl, baseType);
+ RefPtr<WitnessTable> witnessTable = checkConformanceToType(&context, type, inheritanceDecl, baseType);
if(!witnessTable)
return false;
@@ -1707,15 +1701,12 @@ namespace Slang
void SemanticsVisitor::checkExtensionConformance(ExtensionDecl* decl)
{
- if (auto targetDeclRefType = as<DeclRefType>(decl->targetType))
+ auto declRef = createDefaultSubstitutionsIfNeeded(getSession(), makeDeclRef(decl)).as<ExtensionDecl>();
+ auto targetType = GetTargetType(declRef);
+
+ for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
{
- if (auto aggTypeDeclRef = targetDeclRefType->declRef.as<AggTypeDecl>())
- {
- for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
- {
- checkConformance(aggTypeDeclRef, inheritanceDecl);
- }
- }
+ checkConformance(targetType, inheritanceDecl);
}
}
@@ -1740,6 +1731,10 @@ namespace Slang
{
// For non-interface types we need to check conformance.
//
+
+ auto declRef = createDefaultSubstitutionsIfNeeded(getSession(), makeDeclRef(decl)).as<AggTypeDeclBase>();
+ auto type = DeclRefType::Create(getSession(), declRef);
+
// TODO: Need to figure out what this should do for
// `abstract` types if we ever add them. Should they
// be required to implement all interface requirements,
@@ -1747,7 +1742,7 @@ namespace Slang
// (That's what C# does).
for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
{
- checkConformance(makeDeclRef(decl), inheritanceDecl);
+ checkConformance(type, inheritanceDecl);
}
}
}
@@ -2070,7 +2065,7 @@ namespace Slang
void SemanticsVisitor::getGenericParams(
GenericDecl* decl,
List<Decl*>& outParams,
- List<GenericTypeConstraintDecl*> outConstraints)
+ List<GenericTypeConstraintDecl*>& outConstraints)
{
for (auto dd : decl->Members)
{
@@ -2087,100 +2082,223 @@ namespace Slang
}
bool SemanticsVisitor::doGenericSignaturesMatch(
- GenericDecl* fst,
- GenericDecl* snd)
- {
- // First we'll extract the parameters and constraints
- // in each generic signature. We will consider parameters
- // and constraints separately so that we are independent
- // of the order in which constraints are given (that is,
- // a constraint like `<T : IFoo>` would be considered
- // the same as `<T>` with a later `where T : IFoo`.
-
- List<Decl*> fstParams;
- List<GenericTypeConstraintDecl*> fstConstraints;
- getGenericParams(fst, fstParams, fstConstraints);
-
- List<Decl*> sndParams;
- List<GenericTypeConstraintDecl*> sndConstraints;
- getGenericParams(snd, sndParams, sndConstraints);
-
- // For there to be any hope of a match, the
- // two need to have the same number of parameters.
- Index paramCount = fstParams.getCount();
- if (paramCount != sndParams.getCount())
+ GenericDecl* left,
+ GenericDecl* right,
+ RefPtr<GenericSubstitution>* outSubstRightToLeft)
+ {
+ // Our first goal here is to determine if `left` and
+ // `right` have equivalent lists of explicit
+ // generic parameters.
+ //
+ // Once we have determined that the explicit generic
+ // parameters match, we will look at the constraints
+ // placed on those parameters to see if they are
+ // equivalent.
+ //
+ // We thus start by extracting the explicit parameters
+ // and the constraints from each declaration.
+ //
+ List<Decl*> leftParams;
+ List<GenericTypeConstraintDecl*> leftConstraints;
+ getGenericParams(left, leftParams, leftConstraints);
+
+ List<Decl*> rightParams;
+ List<GenericTypeConstraintDecl*> rightConstraints;
+ getGenericParams(right, rightParams, rightConstraints);
+
+ // For there to be any hope of a match, the two decls
+ // need to have the same number of explicit parameters.
+ //
+ Index paramCount = leftParams.getCount();
+ if(paramCount != rightParams.getCount())
return false;
- // Now we'll walk through the parameters.
- for (Index pp = 0; pp < paramCount; ++pp)
+ // Next we will walk through the parameters and look
+ // for a pair-wise match.
+ //
+ for(Index pp = 0; pp < paramCount; ++pp)
{
- Decl* fstParam = fstParams[pp];
- Decl* sndParam = sndParams[pp];
+ Decl* leftParam = leftParams[pp];
+ Decl* rightParam = rightParams[pp];
- if (auto fstTypeParam = as<GenericTypeParamDecl>(fstParam))
+ if (auto leftTypeParam = as<GenericTypeParamDecl>(leftParam))
{
- if (auto sndTypeParam = as<GenericTypeParamDecl>(sndParam))
+ if (auto rightTypeParam = as<GenericTypeParamDecl>(rightParam))
{
- // TODO: is there any validation that needs to be performed here?
- }
- else
- {
- // Type and non-type parameters can't match.
- return false;
+ // Right now any two type parameters are a match.
+ // Names are irrelevant to matching, and any constraints
+ // on the type parameters are represented as implicit
+ // extra parameters of the generic.
+ //
+ // TODO: If we ever supported type parameters with
+ // higher kinds we might need to make a check here
+ // that the kind of each parameter matches (which
+ // would in a sense be a kind of recursive check
+ // of the generic signature of the parameter).
+ //
+ continue;
}
}
- else if (auto fstValueParam = as<GenericValueParamDecl>(fstParam))
+ else if (auto leftValueParam = as<GenericValueParamDecl>(leftParam))
{
- if (auto sndValueParam = as<GenericValueParamDecl>(sndParam))
+ if (auto rightValueParam = as<GenericValueParamDecl>(rightParam))
{
- // Need to check that the parameters have the same type.
+ // In this case we have two generic value parameters,
+ // and they should only be considered to match if
+ // they have the same type.
//
// Note: We are assuming here that the type of a value
// parameter cannot be dependent on any of the type
// parameters in the same signature. This is a reasonable
// assumption for now, but could get thorny down the road.
- if (!fstValueParam->getType()->Equals(sndValueParam->getType()))
+ //
+ if (!leftValueParam->getType()->Equals(rightValueParam->getType()))
{
- // Type mismatch.
+ // If the value parameters have non-matching types,
+ // then the full generic signatures do not match.
+ //
return false;
}
- // TODO: This is not the right place to check on default
- // values for the parameter, because they won't affect
- // the signature, but we should make sure to do validation
- // later on (e.g., that only one declaration can/should
- // be allowed to provide a default).
- }
- else
- {
- // Value and non-value parameters can't match.
- return false;
+ // Generic value parameters with the same type are
+ // always considered to match.
+ //
+ continue;
}
}
+
+ // If we get to this point, then we have two parameters that
+ // were of different syntatic categories (e.g., one type parameter
+ // and one value parameter), so the signatures clearly don't match.
+ //
+ return false;
}
- // If we got this far, then it means the parameter signatures *seem*
- // to match up all right, but now we need to check that the constraints
- // placed on those parameters are also consistent.
- //
- // For now I'm going to assume/require that all declarations must
- // declare the signature in a way that matches exactly.
- Index constraintCount = fstConstraints.getCount();
- if(constraintCount != sndConstraints.getCount())
+ // At this point we know that the explicit generic parameters
+ // of `left` and `right` are aligned, but we need to check
+ // that the constraints that each declaration places on
+ // its parameters match.
+ //
+ // A first challenge that arises is that `left` and `right`
+ // will each express the constraints in terms of their
+ // own parameters. For example, consider the following
+ // declarations:
+ //
+ // void foo1<T : IFoo>(T value);
+ // void foo2<U : IFoo>(U value);
+ //
+ // It is "obvious" to a human that the signatures here
+ // match, but `foo1` has a constraint `T : IFoo` while
+ // `foo2` has a constraint `U : IFoo`, and since `T`
+ // and `U` are distinct `Decl`s, those constraints
+ // are not obviously equivalent.
+ //
+ // We will work around this first issue by creating
+ // a substitution taht lists all the parameters of
+ // `left`, which we can use to specialize `right`
+ // so that it aligns.
+ //
+ // In terms of the example above, this is like constructing
+ // `foo2<T>` so that its constraint, after specialization,
+ // looks like `T : IFoo`.
+ //
+ auto& substRightToLeft = *outSubstRightToLeft;
+ substRightToLeft = createDummySubstitutions(left);
+ substRightToLeft->genericDecl = right;
+
+ // We should now be able to enumerate the constraints
+ // on `right` in a way that uses the same type parameters
+ // as `left`, using `rightDeclRef`.
+ //
+ // At this point a second problem arises: if/when we support
+ // more flexibility in how generic parameter constraints are
+ // specified, it will be possible for two declarations to
+ // list the "same" constraints in very different ways.
+ //
+ // For example, if we support a `where` clause for separating
+ // the constraints from the parameters, then the following
+ // two declarations should have equivalent signatures:
+ //
+ // void foo1<T>(T value)
+ // where T : IFoo
+ // { ... }
+ //
+ // void foo2<T : IFoo>(T value)
+ // { ... }
+ //
+ // Similarly, if we allow for general compositions of interfaces
+ // to be used as constraints, then there can be more than one
+ // way to specify the same constraints:
+ //
+ // void foo1<T : IFoo&IBar>(T value);
+ // void foo2<T : IBar&IFoo>(T value);
+ //
+ // Adding support for equality constraints in `where` clauses
+ // also creates opportunities for multiple equivalent expressions:
+ //
+ // void foo1<T,U>(...) where T.A == U.A;
+ // void foo2<T,U>(...) where U.A == T.A;
+ //
+ // A robsut version of the checking logic here should attempt
+ // to *canonicalize* all of the constraints. Canonicalization
+ // should involve putting constraints into a deterministic
+ // order (e.g., for a generic with `<T,U>` all the constraints
+ // on `T` should come before those on `U`), rewriting individual
+ // constraints into a canonical form (e.g., `T : IFoo & IBar`
+ // should turn into two constraints: `T : IFoo` and `T : IBar`),
+ // etc.
+ //
+ // Once the constraints are in a canonical form we should be able
+ // to test them for pairwise equivalent. As a safety measure we
+ // could also try to test whether one set of constraints implies
+ // the other (since implication in both directions should imply
+ // equivalence, in which case our canonicalization had better
+ // have produced the same result).
+ //
+ // For now we are taking a simpler short-cut by assuming
+ // that constraints are already in a canonical form, which
+ // is reasonable for now as the syntax only allows a single
+ // constraint per parameter, specified on the parameter itself.
+ //
+ // Under the assumption of canonical constraints, we can
+ // assume that different numbers of constraints must indicate
+ // a signature mismatch.
+ //
+ Index constraintCount = leftConstraints.getCount();
+ if(constraintCount != rightConstraints.getCount())
return false;
for (Index cc = 0; cc < constraintCount; ++cc)
{
- //auto fstConstraint = fstConstraints[cc];
- //auto sndConstraint = sndConstraints[cc];
+ // Note that we use a plain `Decl` pointer for the left
+ // constraint, but need to use a `DeclRef` for the right
+ // constraint so that we can take the substitution
+ // arguments into account.
+ //
+ GenericTypeConstraintDecl* leftConstraint = leftConstraints[cc];
+ DeclRef<GenericTypeConstraintDecl> rightConstraint(rightConstraints[cc], substRightToLeft);
+
+ // For now, every constraint has the form `sub : sup`
+ // to indicate that `sub` must be a subtype of `sup`.
+ //
+ // Two such constraints are equivalent if their `sub`
+ // and `sup` types are pairwise equivalent.
+ //
+ auto leftSub = leftConstraint->sub;
+ auto rightSub = GetSub(rightConstraint);
+ if(!leftSub->Equals(rightSub))
+ return false;
- // TODO: the challenge here is that the
- // constraints are going to be expressed
- // in terms of the parameters, which means
- // we need to be doing substitution here.
+ auto leftSup = leftConstraint->sup;
+ auto rightSup = GetSup(rightConstraint);
+ if(!leftSup->Equals(rightSup))
+ return false;
}
- // HACK: okay, we'll just assume things match for now.
+ // If we have checked all of the (canonicalized) constraints
+ // and found them to be pairwise equivalent then the two
+ // generic signatures seem to match.
+ //
return true;
}
@@ -2313,25 +2431,18 @@ namespace Slang
// If we are working with generic functions, then we need to
// consider if their generic signatures match.
+ //
if(newGenericDecl)
{
- SLANG_ASSERT(oldGenericDecl); // already checked above
- if(!doGenericSignaturesMatch(newGenericDecl, oldGenericDecl))
- return SLANG_OK;
-
- // Now we need specialize the declaration references
- // consistently, so that we can compare.
+ // If one declaration is generic, the other must be.
+ // (This condition was already checked above)
//
- // First we create a "dummy" set of substitutions that
- // just reference the parameters of the first generic.
- //
- auto subst = createDummySubstitutions(newGenericDecl);
- //
- // Then we use those parameters to specialize the *other*
- // generic.
- //
- subst->genericDecl = oldGenericDecl;
- oldDeclRef.substitutions.substitutions = subst;
+ SLANG_ASSERT(oldGenericDecl);
+
+ // As part of checking if the generic signatures match,
+ // we will produce a substitution that can be used to
+ // reference `oldGenericDecl` with the generic parameters
+ // substituted for those of `newDecl`.
//
// One way to think about it is that if we have these
// declarations (ignore the name differences...):
@@ -2342,7 +2453,14 @@ namespace Slang
// // newDecl:
// void foo2<U>(U x);
//
- // Then we will compare `foo2` against `foo1<U>`.
+ // Then we will compare the parameter types of `foo2`
+ // against the specialization `foo1<U>`.
+ //
+ RefPtr<GenericSubstitution> subst;
+ if(!doGenericSignaturesMatch(newGenericDecl, oldGenericDecl, &subst))
+ return SLANG_OK;
+
+ oldDeclRef.substitutions.substitutions = subst;
}
// If the parameter signatures don't match, then don't worry