summaryrefslogtreecommitdiffstats
path: root/source/slang/check.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2018-06-12 14:59:13 -0700
committerGitHub <noreply@github.com>2018-06-12 14:59:13 -0700
commit167d8579870db18756c234755b197e4ded930b0e (patch)
tree942f07efe9572699a62df80cadb136a1789780a6 /source/slang/check.cpp
parent7852a2bf0ef5aad0f4f318507c300352c25199f2 (diff)
Initial support for enum declarations (#599)
Slang `enum` declarations will always be scoped, e.g.: ```hlsl enum Color { Red, Green = 2, Blue, } Color c = Color.Red; // Not just `Red` ``` A user can write `enum class` as a placebo for now (to ease sharing of headers with C++). Slang does not currently support the `::` operator for static member lookup, so it must be `Color.Green` and not `Color::Green`. Support for `::` as an alternate syntax could be added later if there is strong user demand. An `enum` type can have a declared "tag type" using syntax like C++ `enum class`: ```hlsl enum MyThings : uint { First = 0, // ... } ``` The `enum` cases will store their values using that type. An `enum` that doesn't declare a tag type will use the type `int` by default. Enum cases are assigned values just like in C/C++: cases can have explicit values, but otherwise default to one more than the previous case, or zero for the first case. All `enum` types will automatically conform to a standard-library `interface` called `__EnumType`, which is used so that basic operators like equality testing can be defined generically for all `enum` types. This change only adds one operator at first (the `==` comparison), but other should be added later. An `enum` case needs to be explicitly converted to an integer where needed (e.g., `int(Color.Red)`). This is implemented by having the main integer types (`int` and `uint`) support built-in initializers that can work for *any* `enum` type (or rather, anything conforming to `__EnumType`). Eventually these will be restricted so that an `enum` type can only be converted to its associated tag type. IR code generation completely eliminates `enum` types and their cases. The `enum` type will be replaced with its tag type, and the cases will be replaced with the tag values. Currently this could leave some mess in the IR where cast operations are applied between values that actually have the same type.
Diffstat (limited to 'source/slang/check.cpp')
-rw-r--r--source/slang/check.cpp504
1 files changed, 493 insertions, 11 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index c3eb44cfd..764764e76 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -359,6 +359,71 @@ namespace Slang
return expr->type->As<DeclRefType>();
}
+ /// Is `decl` usable as a static member?
+ bool isDeclUsableAsStaticMember(
+ Decl* decl)
+ {
+ if(decl->HasModifier<HLSLStaticModifier>())
+ return true;
+
+ if(decl->As<ConstructorDecl>())
+ return true;
+
+ if(decl->As<EnumCaseDecl>())
+ return true;
+
+ if(decl->As<AggTypeDeclBase>())
+ return true;
+
+ if(decl->As<SimpleTypeDecl>())
+ return true;
+
+ return false;
+ }
+
+ /// Is `item` usable as a static member?
+ bool isUsableAsStaticMember(
+ LookupResultItem const& item)
+ {
+ // There's a bit of a gotcha here, because a lookup result
+ // item might include "breadcrumbs" that indicate more steps
+ // along the lookup path. As a result it isn't always
+ // valid to just check whether the final decl is usable
+ // as a static member, because it might not even be a
+ // member of the thing we are trying to work with.
+ //
+
+ Decl* decl = item.declRef.getDecl();
+ for(auto bb = item.breadcrumbs; bb; bb = bb->next)
+ {
+ switch(bb->kind)
+ {
+ // In case lookup went through a `__transparent` member,
+ // we are interested in the static-ness of that transparent
+ // member, and *not* the static-ness of whatever was inside
+ // of it.
+ //
+ // TODO: This would need some work if we ever had
+ // transparent *type* members.
+ //
+ case LookupResultItem::Breadcrumb::Kind::Member:
+ decl = bb->declRef.getDecl();
+ break;
+
+ // TODO: Are there any other cases that need special-case
+ // handling here?
+
+ default:
+ break;
+ }
+ }
+
+ // Okay, we've found the declaration we should actually
+ // be checking, so lets validate that.
+
+ return isDeclUsableAsStaticMember(decl);
+ }
+
RefPtr<Expr> ConstructDeclRefExpr(
DeclRef<Decl> declRef,
RefPtr<Expr> baseExpr,
@@ -378,9 +443,6 @@ namespace Slang
//
if (baseExpr->type->As<TypeType>())
{
- // If the base expression was a type, then that means we
- // are constructing a static member reference.
- //
auto expr = new StaticMemberExpr();
expr->loc = loc;
expr->type = type;
@@ -2417,6 +2479,16 @@ namespace Slang
// with the same name in the type declaration and
// its (known) extensions.
+ // As a first pass, lets check if we already have a
+ // witness in the table for the requirement, so
+ // that we can bail out early.
+ //
+ if(witnessTable->requirementDictionary.ContainsKey(requiredMemberDeclRef.getDecl()))
+ {
+ return true;
+ }
+
+
// An important exception to the above is that an
// inheritance declaration in the interface is not going
// to be satisfied by an inheritance declaration in the
@@ -2530,7 +2602,15 @@ namespace Slang
// *before* we go about checking fine-grained requirements,
// in order to short-circuit any potential for infinite recursion.
- witnessTable = new WitnessTable();
+ // Note: we will re-use the witnes table attached to the inheritance decl,
+ // if there is one. This catches cases where semantic checking might
+ // have synthesized some of the conformance witnesses for us.
+ //
+ witnessTable = inheritanceDecl->witnessTable;
+ if(!witnessTable)
+ {
+ witnessTable = new WitnessTable();
+ }
context->mapInterfaceToWitnessTable.Add(interfaceDeclRef, witnessTable);
bool result = true;
@@ -2770,6 +2850,287 @@ namespace Slang
decl->SetCheckState(getCheckedState());
}
+ // Validate that `type` is a suitable type to use
+ // as the tag type for an `enum`
+ void validateEnumTagType(Type* type, SourceLoc const& loc)
+ {
+ if(auto basicType = type->As<BasicExpressionType>())
+ {
+ switch(basicType->baseType)
+ {
+ default:
+ // By default, don't allow a type to be used
+ // as an `enum` tag type.
+ break;
+
+ case BaseType::Int:
+ case BaseType::UInt:
+ case BaseType::UInt64:
+ // These are all allowed.
+ return;
+ }
+ }
+
+ getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type);
+ }
+
+ void visitEnumDecl(EnumDecl* decl)
+ {
+ if (decl->IsChecked(getCheckedState()))
+ return;
+
+ // Look at inheritance clauses, and
+ // see if one of them is making the enum
+ // "inherit" from a concrete type.
+ // This will become the "tag" type
+ // of the enum.
+ RefPtr<Type> tagType;
+ InheritanceDecl* tagTypeInheritanceDecl = nullptr;
+ for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
+ {
+ checkDecl(inheritanceDecl);
+
+ // Look at the type being inherited from.
+ auto superType = inheritanceDecl->base.type;
+
+ if(auto errorType = superType->As<ErrorType>())
+ {
+ // Ignore any erroneous inheritance clauses.
+ continue;
+ }
+ else if(auto declRefType = superType->As<DeclRefType>())
+ {
+ if(auto interfaceDeclRef = declRefType->declRef.As<InterfaceDecl>())
+ {
+ // Don't consider interface bases as candidates for
+ // the tag type.
+ continue;
+ }
+ }
+
+ if(tagType)
+ {
+ // We already found a tag type.
+ getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType);
+ getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType);
+ break;
+ }
+ else
+ {
+ tagType = superType;
+ tagTypeInheritanceDecl = inheritanceDecl;
+ }
+ }
+
+ // If a tag type has not been set, then we
+ // default it to the built-in `int` type.
+ //
+ // TODO: In the far-flung future we may want to distinguish
+ // `enum` types that have a "raw representation" like this from
+ // ones that are purely abstract and don't expose their
+ // type of their tag.
+ if(!tagType)
+ {
+ tagType = getSession()->getIntType();
+ }
+ else
+ {
+ // TODO: Need to establish that the tag
+ // type is suitable. (e.g., if we are going
+ // to allow raw values for case tags to be
+ // derived automatically, then the tag
+ // type needs to be some kind of interer type...)
+ //
+ // For now we will just be harsh and require it
+ // to be one of a few builtin types.
+ validateEnumTagType(tagType, tagTypeInheritanceDecl->loc);
+ }
+ decl->tagType = tagType;
+
+
+ // An `enum` type should automatically conform to the `__EnumType` interface.
+ // The compiler needs to insert this conformance behind the scenes, and this
+ // seems like the best place to do it.
+ {
+ // First, look up the type of the `__EnumType` interface.
+ RefPtr<Type> enumTypeType = getSession()->getEnumTypeType();
+
+ RefPtr<InheritanceDecl> enumConformanceDecl = new InheritanceDecl();
+ enumConformanceDecl->ParentDecl = decl;
+ enumConformanceDecl->loc = decl->loc;
+ enumConformanceDecl->base.type = getSession()->getEnumTypeType();
+ decl->Members.Add(enumConformanceDecl);
+
+ // The `__EnumType` interface has one required member, the `__Tag` type.
+ // We need to satisfy this requirement automatically, rather than require
+ // the user to actually declare a member with this name (otherwise we wouldn't
+ // let them define a tag value with the name `__Tag`).
+ //
+ RefPtr<WitnessTable> witnessTable = new WitnessTable();
+ enumConformanceDecl->witnessTable = witnessTable;
+
+ Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag");
+ Decl* tagAssociatedTypeDecl = nullptr;
+ if(auto enumTypeTypeDeclRefType = enumTypeType.As<DeclRefType>())
+ {
+ if(auto enumTypeTypeInterfaceDecl = enumTypeTypeDeclRefType->declRef.getDecl()->As<InterfaceDecl>())
+ {
+ for(auto memberDecl : enumTypeTypeInterfaceDecl->Members)
+ {
+ if(memberDecl->getName() == tagAssociatedTypeName)
+ {
+ tagAssociatedTypeDecl = memberDecl;
+ break;
+ }
+ }
+ }
+ }
+ if(!tagAssociatedTypeDecl)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'");
+ }
+
+ // Okay, add the conformance withess for `__Tag` being satisfied by `tagType`
+ witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType));
+
+ // TODO: we actually also need to synthesize a witness for the conformance of `tagType`
+ // to the `__BuiltinIntegerType` interface, because that is a constraint on the
+ // associated type `__Tag`.
+
+ // TODO: eventually we should consider synthesizing other requirements for
+ // the min/max tag values, or the total number of tags, so that people don't
+ // have to declare these as additional cases.
+
+ enumConformanceDecl->SetCheckState(DeclCheckState::Checked);
+ }
+
+
+ decl->SetCheckState(DeclCheckState::CheckedHeader);
+
+ auto enumType = DeclRefType::Create(
+ getSession(),
+ makeDeclRef(decl));
+
+ // Check the enum cases in order.
+ for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
+ {
+ // Each case defines a value of the enum's type.
+ //
+ // TODO: If we ever support enum cases with payloads,
+ // then they would probably have a type that is a
+ // `FunctionType` from the payload types to the
+ // enum type.
+ //
+ caseDecl->type.type = enumType;
+
+ checkDecl(caseDecl);
+ }
+
+ // For any enum case that didn't provide an explicit
+ // tag value, derived an appropriate tag value.
+ IntegerLiteralValue defaultTag = 0;
+ for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
+ {
+ if(auto explicitTagValExpr = caseDecl->initExpr)
+ {
+ // This tag has an initializer, so it should establish
+ // the tag value for a successor case that doesn't
+ // provide an explicit tag.
+
+ RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr);
+ if(explicitTagVal)
+ {
+ if(auto constIntVal = explicitTagVal.As<ConstantIntVal>())
+ {
+ defaultTag = constIntVal->value;
+ }
+ else
+ {
+ // TODO: need to handle other possibilities here
+ getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr);
+ }
+ }
+ else
+ {
+ // If this happens, then the explicit tag value expression
+ // doesn't seem to be a constant after all. In this case
+ // we expect the checking logic to have applied already.
+ }
+ }
+ else
+ {
+ // This tag has no initializer, so it should use
+ // the default tag value we are tracking.
+ RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr();
+ tagValExpr->loc = caseDecl->loc;
+ tagValExpr->type = QualType(tagType);
+ tagValExpr->value = defaultTag;
+
+ caseDecl->initExpr = tagValExpr;
+ }
+
+ // Default tag for the next case will be one more than
+ // for the most recent case.
+ //
+ // TODO: We might consider adding a `[flags]` attribute
+ // that modifies this behavior to be `defaultTagForCase <<= 1`.
+ //
+ defaultTag++;
+ }
+
+ // Now check any other member declarations.
+ for(auto memberDecl : decl->Members)
+ {
+ // Already checked inheritance declarations above.
+ if(auto inheritanceDecl = memberDecl->As<InheritanceDecl>())
+ continue;
+
+ // Already checked enum case declarations above.
+ if(auto caseDecl = memberDecl->As<EnumCaseDecl>())
+ continue;
+
+ // TODO: Right now we don't support other kinds of
+ // member declarations on an `enum`, but that is
+ // something we may want to allow in the long run.
+ //
+ checkDecl(memberDecl);
+ }
+ decl->SetCheckState(getCheckedState());
+ }
+
+ void visitEnumCaseDecl(EnumCaseDecl* decl)
+ {
+ if (decl->IsChecked(getCheckedState()))
+ return;
+
+ // An enum case had better appear inside an enum!
+ //
+ // TODO: Do we need/want to support generic cases some day?
+ auto parentEnumDecl = decl->ParentDecl->As<EnumDecl>();
+ SLANG_ASSERT(parentEnumDecl);
+
+ // The tag type should have already been set by
+ // the surrounding `enum` declaration.
+ auto tagType = parentEnumDecl->tagType;
+ SLANG_ASSERT(tagType);
+
+ // Need to check the init expression, if present, since
+ // that represents the explicit tag for this case.
+ if(auto initExpr = decl->initExpr)
+ {
+ initExpr = CheckExpr(initExpr);
+ initExpr = Coerce(tagType, initExpr);
+
+ // We want to enforce that this is an integer constant
+ // expression, but we don't actually care to retain
+ // the value.
+ CheckIntegerConstantExpression(initExpr);
+
+ decl->initExpr = initExpr;
+ }
+ decl->SetCheckState(getCheckedState());
+ }
+
void visitDeclGroup(DeclGroup* declGroup)
{
for (auto decl : declGroup->decls)
@@ -3988,14 +4349,26 @@ namespace Slang
// or NULL if the expression isn't recognized as a constant.
RefPtr<IntVal> TryCheckIntegerConstantExpression(Expr* exp)
{
- if (!exp->type.type->Equals(getSession()->getIntType()))
+ // Check if type is acceptable for an integer constant expression
+ if(auto basicType = exp->type.type->As<BasicExpressionType>())
+ {
+ switch(basicType->baseType)
+ {
+ default:
+ return nullptr;
+
+ case BaseType::Int:
+ case BaseType::UInt:
+ case BaseType::UInt64:
+ break;
+ }
+ }
+ else
{
return nullptr;
}
-
-
- // Otherwise, we need to consider operations that we might be able to constant-fold...
+ // Consider operations that we might be able to constant-fold...
return TryConstantFoldExpr(exp);
}
@@ -4637,6 +5010,8 @@ namespace Slang
if( auto aggTypeDeclRef = declRef.As<AggTypeDecl>() )
{
+ checkDecl(aggTypeDeclRef.getDecl());
+
for( auto inheritanceDeclRef : getMembersOfTypeWithExt<InheritanceDecl>(aggTypeDeclRef))
{
checkDecl(inheritanceDeclRef.getDecl());
@@ -5035,7 +5410,7 @@ namespace Slang
return resultSubst;
}
-
+
// State related to overload resolution for a call
// to an overloaded symbol
struct OverloadResolveContext
@@ -6356,6 +6731,30 @@ namespace Slang
AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType);
}
+ // Also check for generic constructors.
+ //
+ // TODO: There is way too much duplication between this case and the extension
+ // handling below, and all of this is *also* duplicative with the ordinary
+ // overload resolution logic for function.
+ //
+ // The right solution is to handle a "constructor" call expression by
+ // first doing member lookup in the type (for initializer members, which
+ // should all share a common name), and then to do overload resolution using
+ // the (possibly overloaded) result of that lookup.
+ //
+ for (auto genericDeclRef : getMembersOfType<GenericDecl>(aggTypeDeclRef))
+ {
+ if (auto ctorDecl = genericDeclRef.getDecl()->inner.As<ConstructorDecl>())
+ {
+ DeclRef<Decl> innerRef = SpecializeGenericForOverload(genericDeclRef, context);
+ if (!innerRef)
+ continue;
+
+ DeclRef<ConstructorDecl> innerCtorRef = innerRef.As<ConstructorDecl>();
+ AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType);
+ }
+ }
+
// Now walk through any extensions we can find for this types
for (auto ext = GetCandidateExtensions(aggTypeDeclRef); ext; ext = ext->nextCandidateExtension)
{
@@ -7460,8 +7859,91 @@ namespace Slang
return lookupResultFailure(expr, baseType);
}
- // TODO: need to filter for declarations that are valid to refer
- // to in this context...
+ // We need to confirm that whatever member we
+ // are trying to refer to is usable via static reference.
+ //
+ // TODO: eventually we might allow a non-static
+ // member to be adapted by turning it into something
+ // like a closure that takes the missing `this` parameter.
+ //
+ // E.g., a static reference to a method could be treated
+ // as a value with a function type, where the first parameter
+ // is `type`.
+ //
+ // The biggest challenge there is that we'd need to arrange
+ // to generate "dispatcher" functions that could be used
+ // to implement that function, in the case where we are
+ // making a static reference to some kind of polymoprhic declaration.
+ //
+ // (Also, static refernces to fields/properties would get even
+ // harder, because you'd have to know whether a getter/setter/ref-er
+ // is needed).
+ //
+ // For now let's just be expedient and disallow all of that, because
+ // we can always add it back in later.
+
+ if(!lookupResult.isOverloaded())
+ {
+ // The non-overloaded case is relatively easy. We just want
+ // to look at the member being referenced, and check if
+ // it is allowed in a `static` context:
+ //
+ if(!isUsableAsStaticMember(lookupResult.item))
+ {
+ getSink()->diagnose(
+ expr->loc,
+ Diagnostics::staticRefToNonStaticMember,
+ type,
+ expr->name);
+ }
+ }
+ else
+ {
+ // The overloaded case is trickier, because we should first
+ // filter the list of candidates, because if there is anything
+ // that *is* usable in a static context, then we should assume
+ // the user just wants to reference that. We should only
+ // issue an error if *all* of the items that were discovered
+ // are non-static.
+ bool anyNonStatic = false;
+ List<LookupResultItem> staticItems;
+ for(auto item : lookupResult.items)
+ {
+ // Is this item usable as a static member?
+ if(isUsableAsStaticMember(item))
+ {
+ // If yes, then it will be part of the output.
+ staticItems.Add(item);
+ }
+ else
+ {
+ // If no, then we might need to output an error.
+ anyNonStatic = true;
+ }
+ }
+
+ // Was there anything non-static in the list?
+ if(anyNonStatic)
+ {
+ // If we had some static items, then that's okay,
+ // we just want to use our newly-filtered list.
+ if(staticItems.Count())
+ {
+ lookupResult.items = staticItems;
+ }
+ else
+ {
+ // Otherwise, it is time to report an error.
+ getSink()->diagnose(
+ expr->loc,
+ Diagnostics::staticRefToNonStaticMember,
+ type,
+ expr->name);
+ }
+ }
+ // If there were no non-static items, then the `items`
+ // array already represents what we'd get by filtering...
+ }
return createLookupResultExpr(
lookupResult,