summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/check.cpp504
-rw-r--r--source/slang/compiler.h10
-rw-r--r--source/slang/core.meta.slang45
-rw-r--r--source/slang/core.meta.slang.h48
-rw-r--r--source/slang/decl-defs.h15
-rw-r--r--source/slang/diagnostic-defs.h12
-rw-r--r--source/slang/lower-to-ir.cpp75
-rw-r--r--source/slang/parser.cpp158
-rw-r--r--source/slang/syntax.cpp13
-rw-r--r--source/slang/type-defs.h9
10 files changed, 848 insertions, 41 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,
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index 8e578f95f..3a5f888fe 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -113,8 +113,8 @@ namespace Slang
// The name of the entry point function (e.g., `main`)
Name* name;
-
- // The type names we want to substitute into the
+
+ // The type names we want to substitute into the
// global generic type parameters
List<String> genericParameterTypeNames;
@@ -373,7 +373,7 @@ namespace Slang
~CompileRequest();
RefPtr<Expr> parseTypeString(TranslationUnitRequest * translationUnit, String typeStr, RefPtr<Scope> scope);
-
+
Type* getTypeFromString(String typeStr);
void parseTranslationUnit(
@@ -526,6 +526,8 @@ namespace Slang
Type* getErrorType();
Type* getStringType();
+ Type* getEnumTypeType();
+
// Construct the type `Ptr<valueType>`, where `Ptr`
// is looked up as a builtin type.
RefPtr<PtrType> getPtrType(RefPtr<Type> valueType);
@@ -572,4 +574,4 @@ namespace Slang
}
-#endif \ No newline at end of file
+#endif
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index 507ae014e..a29cf7a27 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -28,6 +28,23 @@ interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithme
__init(float value);
}
+// A type resulting from an `enum` declaration.
+__magic_type(EnumTypeType)
+interface __EnumType
+{
+ // The type of tags for this `enum`
+ //
+ // Note: using `__Tag` instead of `Tag` to avoid any
+ // conflict if a user had an `enum` case called `Tag`
+ associatedtype __Tag : __BuiltinIntegerType;
+};
+
+// A type resulting from an `enum` declaration
+// with the `[flags]` attribute.
+interface __FlagsEnumType : __EnumType
+{
+};
+
__generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right);
__generic<T> __intrinsic_op(select) T operator?:(bool condition, T ifTrue, T ifFalse);
@@ -92,6 +109,28 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt)
sb << "__init(" << kBaseTypes[ss].name << " value);\n";
}
+ // If this is a basic integer type, then define explicit
+ // initializers that take a value of an `enum` type.
+ //
+ // TODO: This should actually be restricted, so that this
+ // only applies `where T.__Tag == Self`, but we don't have
+ // the needed features in our type system to implement
+ // that constraint right now.
+ //
+ switch (kBaseTypes[tt].tag)
+ {
+ case BaseType::Int:
+ case BaseType::UInt:
+}}}}
+ __generic<T:__EnumType>
+ __init(T value);
+${{{{
+ break;
+
+ default:
+ break;
+ }
+
sb << "};\n";
}
@@ -1050,6 +1089,12 @@ for (auto op : binaryOps)
}
}}}}
+// Operators to apply to `enum` types
+
+__generic<E : __EnumType>
+__intrinsic_op($(kIROp_Eql))
+bool operator==(E left, E right);
+
// Statement Attributes
__attributeTarget(LoopStmt)
diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h
index 948ca4207..bdd618976 100644
--- a/source/slang/core.meta.slang.h
+++ b/source/slang/core.meta.slang.h
@@ -28,6 +28,23 @@ SLANG_RAW(" // a floating-point value...\n")
SLANG_RAW(" __init(float value);\n")
SLANG_RAW("}\n")
SLANG_RAW("\n")
+SLANG_RAW("// A type resulting from an `enum` declaration.\n")
+SLANG_RAW("__magic_type(EnumTypeType)\n")
+SLANG_RAW("interface __EnumType\n")
+SLANG_RAW("{\n")
+SLANG_RAW(" // The type of tags for this `enum`\n")
+SLANG_RAW(" //\n")
+SLANG_RAW(" // Note: using `__Tag` instead of `Tag` to avoid any\n")
+SLANG_RAW(" // conflict if a user had an `enum` case called `Tag`\n")
+SLANG_RAW(" associatedtype __Tag : __BuiltinIntegerType;\n")
+SLANG_RAW("};\n")
+SLANG_RAW("\n")
+SLANG_RAW("// A type resulting from an `enum` declaration\n")
+SLANG_RAW("// with the `[flags]` attribute.\n")
+SLANG_RAW("interface __FlagsEnumType : __EnumType\n")
+SLANG_RAW("{\n")
+SLANG_RAW("};\n")
+SLANG_RAW("\n")
SLANG_RAW("__generic<T,U> __intrinsic_op(Sequence) U operator,(T left, U right);\n")
SLANG_RAW("\n")
SLANG_RAW("__generic<T> __intrinsic_op(select) T operator?:(bool condition, T ifTrue, T ifFalse);\n")
@@ -92,6 +109,28 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt)
sb << "__init(" << kBaseTypes[ss].name << " value);\n";
}
+ // If this is a basic integer type, then define explicit
+ // initializers that take a value of an `enum` type.
+ //
+ // TODO: This should actually be restricted, so that this
+ // only applies `where T.__Tag == Self`, but we don't have
+ // the needed features in our type system to implement
+ // that constraint right now.
+ //
+ switch (kBaseTypes[tt].tag)
+ {
+ case BaseType::Int:
+ case BaseType::UInt:
+SLANG_RAW("\n")
+SLANG_RAW(" __generic<T:__EnumType>\n")
+SLANG_RAW(" __init(T value);\n")
+
+ break;
+
+ default:
+ break;
+ }
+
sb << "};\n";
}
@@ -1065,6 +1104,15 @@ for (auto op : binaryOps)
}
SLANG_RAW("\n")
SLANG_RAW("\n")
+SLANG_RAW("// Operators to apply to `enum` types\n")
+SLANG_RAW("\n")
+SLANG_RAW("__generic<E : __EnumType>\n")
+SLANG_RAW("__intrinsic_op(")
+SLANG_SPLICE(kIROp_Eql
+)
+SLANG_RAW(")\n")
+SLANG_RAW("bool operator==(E left, E right);\n")
+SLANG_RAW("\n")
SLANG_RAW("// Statement Attributes\n")
SLANG_RAW("\n")
SLANG_RAW("__attributeTarget(LoopStmt)\n")
diff --git a/source/slang/decl-defs.h b/source/slang/decl-defs.h
index 2f4f5abd3..502412b4b 100644
--- a/source/slang/decl-defs.h
+++ b/source/slang/decl-defs.h
@@ -87,6 +87,19 @@ SIMPLE_SYNTAX_CLASS(StructDecl, AggTypeDecl)
SIMPLE_SYNTAX_CLASS(ClassDecl, AggTypeDecl)
+// TODO: Is it appropriate to treat an `enum` as an aggregate type?
+// Most code that looks for, e.g., conformances assumes user-defined
+// types are all `AggTypeDecl`, so this is the right choice for now
+// if we want `enum` types to be able to implement interfaces, etc.
+//
+SYNTAX_CLASS(EnumDecl, AggTypeDecl)
+RAW(
+ RefPtr<Type> tagType;
+)
+END_SYNTAX_CLASS()
+
+SIMPLE_SYNTAX_CLASS(EnumCaseDecl, VarDeclBase)
+
// An interface which other types can conform to
SIMPLE_SYNTAX_CLASS(InterfaceDecl, AggTypeDecl)
@@ -135,7 +148,7 @@ END_SYNTAX_CLASS()
SYNTAX_CLASS(AssocTypeDecl, AggTypeDecl)
END_SYNTAX_CLASS()
-// A '__generic_param' declaration, which defines a generic
+// A '__generic_param' declaration, which defines a generic
// entry-point parameter. Is a container of GenericTypeConstraintDecl
SYNTAX_CLASS(GlobalGenericParamDecl, AggTypeDecl)
END_SYNTAX_CLASS()
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index 2bfed3d52..b6f755489 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -197,8 +197,10 @@ DIAGNOSTIC(30049, Note, thisIsImmutableByDefault, "a 'this' parameter is curren
DIAGNOSTIC(30051, Error, invalidValueForArgument, "invalid value for argument '$0'")
DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on type '$1'")
-DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.")
+DIAGNOSTIC(30100, Error, staticRefToNonStaticMember, "type '$0' cannot be used to refer to non-static member '$1'")
+
+DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.")
DIAGNOSTIC(33071, Error, expectedAStringLiteral, "expected a string literal")
// Attributes
@@ -208,6 +210,14 @@ DIAGNOSTIC(31001, Error, attributeNotApplicable, "attribute '$0' is not valid he
DIAGNOSTIC(31100, Error, unknownStageName, "unknown stage name '$0'")
+// Enums
+
+DIAGNOSTIC(32000, Error, invalidEnumTagType, "invalid tag type for 'enum': '$0'")
+DIAGNOSTIC(32001, Error, enumTypeAlreadyHasTagType, "'enum' type has already declared a tag type")
+DIAGNOSTIC(32002, Note, seePreviousTagType, "see previous tag type declaration")
+DIAGNOSTIC(32003, Error, unexpectedEnumTagExpr, "unexpected form for 'enum' tag value expression")
+
+
// 303xx: interfaces and associated types
DIAGNOSTIC(30300, Error, assocTypeInInterfaceOnly, "'associatedtype' can only be defined in an 'interface'.")
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 12ba94146..761fe9c91 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -2668,6 +2668,9 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
// so we need to track a bit of extra data:
struct SwitchStmtInfo
{
+ // The block that will be made to contain the `switch` statement
+ IRBlock* initialBlock = nullptr;
+
// The label for the `default` case, if any.
IRBlock* defaultLabel = nullptr;
@@ -2757,12 +2760,21 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
// for the best.
//
// TODO: figure out something cleaner.
- auto caseVal = getSimpleVal(context, lowerRValueExpr(context, caseStmt->expr));
+
+ // Actually, one gotcha is that if we ever allow non-constant
+ // expressions here (or anything that requires instructions
+ // to be emitted to yield its value), then those instructions
+ // need to go into an appropriate block.
+
+ IRGenContext subContext = *context;
+ IRBuilder subBuilder = *getBuilder();
+ subBuilder.setInsertInto(info->initialBlock);
+ subContext.irBuilder = &subBuilder;
+ auto caseVal = getSimpleVal(context, lowerRValueExpr(&subContext, caseStmt->expr));
// Figure out where we are branching to.
auto label = getLabelForCase(info);
-
// Add this `case` to the list for the enclosing `switch`.
info->cases.Add(caseVal);
info->cases.Add(label);
@@ -2863,6 +2875,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
// Iterate over the body of the statement, looking
// for `case` or `default` statements:
SwitchStmtInfo info;
+ info.initialBlock = initialBlock;
info.defaultLabel = nullptr;
lowerSwitchCases(stmt->body, &info);
@@ -3760,6 +3773,59 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
setMangledName(inst, context->getSession()->getNameObj(name));
}
+ LoweredValInfo visitEnumCaseDecl(EnumCaseDecl* decl)
+ {
+ // A case within an `enum` decl will lower to a value
+ // of the `enum`'s "tag" type.
+ //
+ // TODO: a bit more work will be needed if we allow for
+ // enum cases that have payloads, because then we need
+ // a function that constructs the value given arguments.
+
+ IRBuilder subBuilderStorage = *getBuilder();
+ IRBuilder* subBuilder = &subBuilderStorage;
+
+ // Emit any generics that should wrap the actual type.
+ emitOuterGenerics(subBuilder, decl, decl);
+
+ IRGenContext subContextStorage = *context;
+ IRGenContext* subContext = &subContextStorage;
+ subContext->irBuilder = subBuilder;
+
+ return lowerRValueExpr(subContext, decl->initExpr);
+ }
+
+ LoweredValInfo visitEnumDecl(EnumDecl* decl)
+ {
+ // Given a declaration of a type, we need to make sure
+ // to output "witness tables" for any interfaces this
+ // type has declared conformance to.
+ for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
+ {
+ ensureDecl(context, inheritanceDecl);
+ }
+
+ IRBuilder subBuilderStorage = *getBuilder();
+ IRBuilder* subBuilder = &subBuilderStorage;
+ emitOuterGenerics(subBuilder, decl, decl);
+
+ IRGenContext subContextStorage = *context;
+ IRGenContext* subContext = &subContextStorage;
+ subContext->irBuilder = subBuilder;
+
+ // An `enum` declaration will currently lower directly to its "tag"
+ // type, so that any references to the `enum` become referenes to
+ // the tag type instead.
+ //
+ // TODO: if we ever support `enum` types with payloads, we would
+ // need to make the `enum` lower to some kind of custom "tagged union"
+ // type.
+
+ IRType* loweredTagType = lowerType(subContext, decl->tagType);
+
+ return LoweredValInfo::simple(finishOuterGenerics(subBuilder, loweredTagType));
+ }
+
LoweredValInfo visitAggTypeDecl(AggTypeDecl* decl)
{
// Don't generate an IR `struct` for intrinsic types
@@ -3768,11 +3834,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo();
}
- if(getMangledName(decl) == "_ST03int")
- {
- decl = decl;
- }
-
// Given a declaration of a type, we need to make sure
// to output "witness tables" for any interfaces this
// type has declared conformance to.
diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp
index ad4eef9a6..fee5d04b6 100644
--- a/source/slang/parser.cpp
+++ b/source/slang/parser.cpp
@@ -127,6 +127,8 @@ namespace Slang
ContainerDecl* containerDecl,
TokenType closingToken);
+ static RefPtr<Decl> parseEnumDecl(Parser* parser);
+
// Parse the `{}`-delimeted body of an aggregate type declaration
static void parseAggTypeDeclBody(
Parser* parser,
@@ -215,7 +217,7 @@ namespace Slang
return tokenType;
}
- // Skip balanced
+ // Skip balanced
static TokenType SkipToMatchingToken(
TokenReader* reader,
TokenType tokenType)
@@ -403,7 +405,7 @@ namespace Slang
// Skip balanced tokens and try again.
TokenType skipped = SkipBalancedToken(tokenReader);
-
+
// If we happened to find a matched pair of tokens, and
// the end of it was a token we were looking for,
// then recover here
@@ -1097,7 +1099,7 @@ namespace Slang
{
public:
RefPtr<Scope> scope;
- void visitDeclRefExpr(DeclRefExpr* expr)
+ void visitDeclRefExpr(DeclRefExpr* expr)
{
expr->scope = scope;
}
@@ -1161,7 +1163,7 @@ namespace Slang
}
else
parseFuncDeclHeaderInner(nullptr);
-
+
return retDecl;
}
@@ -1471,7 +1473,7 @@ namespace Slang
RefPtr<Decl> newDecl)
{
SLANG_ASSERT(newDecl);
-
+
if( decl )
{
group = new DeclGroup();
@@ -1655,11 +1657,24 @@ namespace Slang
{
TypeSpec typeSpec;
- // We may see a `struct` type specified here, and need to act accordingly
+ // We may see a `struct` (or `enum` or `class`) tag specified here, and need to act accordingly
//
// TODO(tfoley): Handle the case where the user is just using `struct`
// as a way to name an existing struct "tag" (e.g., `struct Foo foo;`)
//
+ // TODO: We should really make these keywords be registered like any other
+ // syntax category, rather than be special-cased here. The main issue here
+ // is that we need to allow them to be used as type specififers, as in:
+ //
+ // struct Foo { int x } foo;
+ //
+ // The ideal answer would be to register certain keywords as being able
+ // to parse a type specififer, and look for those keywords here.
+ // We should ideally add special case logic that bails out of declarator
+ // parsing iff we have one of these kinds of type specififers and the
+ // closing `}` is at the end of its line, as a bit of a special case
+ // to allow the common idiom.
+ //
if( parser->LookAheadToken("struct") )
{
auto decl = parser->ParseStruct();
@@ -1674,6 +1689,13 @@ namespace Slang
typeSpec.expr = createDeclRefType(parser, decl);
return typeSpec;
}
+ else if(parser->LookAheadToken("enum"))
+ {
+ auto decl = parseEnumDecl(parser);
+ typeSpec.decl = decl;
+ typeSpec.expr = createDeclRefType(parser, decl);
+ return typeSpec;
+ }
Token typeName = parser->ReadToken(TokenType::Identifier);
@@ -1742,6 +1764,51 @@ namespace Slang
return result;
}
+ // It is possible that we have a plain `struct`, `enum`,
+ // or similar declaration that isn't being used to declare
+ // any variable, and the user didn't put a trailing
+ // semicolon on it:
+ //
+ // struct Batman
+ // {
+ // int cape;
+ // }
+ //
+ // We want to allow this syntax (rather than give an
+ // inscrutable error), but also support the less common
+ // idiom where that declaration is used as part of
+ // a variable declaration:
+ //
+ // struct Robin
+ // {
+ // float tights;
+ // } boyWonder;
+ //
+ // As a bit of a hack (insofar as it means we aren't
+ // *really* compatible with arbitrary HLSL code), we
+ // will check if there are any more tokens on the
+ // same line as the closing `}`, and if not, we
+ // will treat it like the end of the declaration.
+ //
+ // Just as a safety net, only apply this logic for
+ // a file that is being passed in as "true" Slang code.
+ //
+ if(parser->translationUnit->sourceLanguage == SourceLanguage::Slang)
+ {
+ if(typeSpec.decl)
+ {
+ if(peekToken(parser).flags & TokenFlag::AtStartOfLine)
+ {
+ // The token after the `}` is at the start of its
+ // own line, which means it can't be on the same line.
+ //
+ // This means the programmer probably wants to
+ // just treat this as a declaration.
+ return declGroupBuilder.getResult();
+ }
+ }
+ }
+
InitDeclarator initDeclarator = ParseInitDeclarator(parser);
@@ -1978,7 +2045,7 @@ namespace Slang
//
// However, that is an uncommon occurence, and trying
// to continue parsing semantics here even if we didn't
- // see a colon forces us to be careful about
+ // see a colon forces us to be careful about
// avoiding an infinite loop here.
if (!AdvanceIf(parser, TokenType::Colon))
{
@@ -2921,6 +2988,71 @@ namespace Slang
return rs;
}
+ static RefPtr<EnumCaseDecl> parseEnumCaseDecl(Parser* parser)
+ {
+ RefPtr<EnumCaseDecl> decl = new EnumCaseDecl();
+ decl->nameAndLoc = expectIdentifier(parser);
+
+ if(AdvanceIf(parser, TokenType::OpAssign))
+ {
+ decl->initExpr = parser->ParseArgExpr();
+ }
+
+ return decl;
+ }
+
+ static RefPtr<Decl> parseEnumDecl(Parser* parser)
+ {
+ RefPtr<EnumDecl> decl = new EnumDecl();
+ parser->FillPosition(decl);
+
+ parser->ReadToken("enum");
+
+ // HACK: allow the user to write `enum class` in case
+ // they are trying to share a header between C++ and Slang.
+ //
+ // TODO: diagnose this with a warning some day, and move
+ // toward deprecating it.
+ //
+ AdvanceIf(parser, "class");
+
+ decl->nameAndLoc = expectIdentifier(parser);
+
+ auto parseEnumDeclInner = [&](GenericDecl*)
+ {
+ parseOptionalInheritanceClause(parser, decl);
+ parser->ReadToken(TokenType::LBrace);
+
+ while(!AdvanceIfMatch(parser, TokenType::RBrace))
+ {
+ RefPtr<EnumCaseDecl> caseDecl = parseEnumCaseDecl(parser);
+ AddMember(decl, caseDecl);
+
+ if(AdvanceIf(parser, TokenType::RBrace))
+ break;
+
+ parser->ReadToken(TokenType::Comma);
+ }
+ return decl;
+ };
+
+ if (parser->LookAheadToken(TokenType::OpLess))
+ {
+ RefPtr<GenericDecl> genericDecl = new GenericDecl();
+ parser->FillPosition(genericDecl);
+ parser->PushScope(genericDecl);
+ ParseGenericDeclImpl(parser, genericDecl, parseEnumDeclInner);
+ parser->PopScope();
+ return genericDecl;
+ }
+ else
+ {
+ parseEnumDeclInner(nullptr);
+ }
+
+ return decl;
+ }
+
static RefPtr<Stmt> ParseSwitchStmt(Parser* parser)
{
RefPtr<SwitchStmt> stmt = new SwitchStmt();
@@ -3205,7 +3337,7 @@ namespace Slang
Modifiers modifiers)
{
RefPtr<DeclStmt>varDeclrStatement = new DeclStmt();
-
+
FillPosition(varDeclrStatement.Ptr());
auto decl = ParseDeclWithModifiers(this, currentScope->containerDecl, modifiers);
varDeclrStatement->decl = decl;
@@ -3348,10 +3480,10 @@ namespace Slang
RefPtr<ExpressionStmt> Parser::ParseExpressionStatement()
{
RefPtr<ExpressionStmt> statement = new ExpressionStmt();
-
+
FillPosition(statement.Ptr());
statement->Expression = ParseExpression();
-
+
ReadToken(TokenType::Semicolon);
return statement;
}
@@ -3544,7 +3676,7 @@ namespace Slang
for(;;)
{
auto nextOpPrec = GetOpLevel(parser, parser->tokenReader.PeekTokenType());
-
+
if((GetAssociativityFromLevel(nextOpPrec) == Associativity::Right) ? (nextOpPrec < opPrec) : (nextOpPrec <= opPrec))
break;
@@ -3632,7 +3764,7 @@ namespace Slang
}
#endif
}
-
+
// We *might* be looking at an application of a generic to arguments,
// but we need to disambiguate to make sure.
static RefPtr<Expr> maybeParseGenericApp(
@@ -4021,7 +4153,7 @@ namespace Slang
parser->FillPosition(memberExpr.Ptr());
memberExpr->BaseExpression = expr;
- parser->ReadToken(TokenType::Dot);
+ parser->ReadToken(TokenType::Dot);
memberExpr->name = expectIdentifier(parser).name;
if (peekTokenType(parser) == TokenType::OpLess)
diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp
index 74c817b92..db0410cbd 100644
--- a/source/slang/syntax.cpp
+++ b/source/slang/syntax.cpp
@@ -286,6 +286,13 @@ void Type::accept(IValVisitor* visitor, void* extra)
return DeclRefType::Create(this, makeDeclRef<Decl>(stringTypeDecl));
}
+ Type* Session::getEnumTypeType()
+ {
+ auto enumTypeTypeDecl = findMagicDecl(this, "EnumTypeType");
+ return DeclRefType::Create(this, makeDeclRef<Decl>(enumTypeTypeDecl));
+ }
+
+
RefPtr<PtrType> Session::getPtrType(
RefPtr<Type> valueType)
{
@@ -1618,7 +1625,7 @@ void Type::accept(IValVisitor* visitor, void* extra)
newSubst->paramDecl = appGlobalGenericSubst->paramDecl;
newSubst->actualType = appGlobalGenericSubst->actualType;
newSubst->constraintArgs = appGlobalGenericSubst->constraintArgs;
-
+
*link = newSubst;
link = &newSubst->outer;
}
@@ -1834,7 +1841,7 @@ void Type::accept(IValVisitor* visitor, void* extra)
&diff);
if (!diff)
- return *this;
+ return *this;
*ioDiff += diff;
@@ -2413,7 +2420,7 @@ void Type::accept(IValVisitor* visitor, void* extra)
// TODO: need to print out substitutions too!
return name->text;
}
-
+
bool SubstitutionSet::Equals(SubstitutionSet substSet) const
{
if(!substitutions || !substSet.substitutions)
diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h
index c7b0004e6..65d015560 100644
--- a/source/slang/type-defs.h
+++ b/source/slang/type-defs.h
@@ -328,6 +328,13 @@ END_SYNTAX_CLASS()
// The built-in `String` type
SIMPLE_SYNTAX_CLASS(StringType, BuiltinType)
+// Type built-in `__EnumType` type
+SYNTAX_CLASS(EnumTypeType, BuiltinType)
+
+// TODO: provide accessors for the declaration, the "tag" type, etc.
+
+END_SYNTAX_CLASS()
+
// Base class for types that map down to
// simple pointers as part of code generation.
SYNTAX_CLASS(PtrTypeBase, BuiltinType)
@@ -424,7 +431,7 @@ SYNTAX_CLASS(GenericDeclRefType, Type)
: declRef(declRef)
{}
-
+
DeclRef<GenericDecl> const& GetDeclRef() const { return declRef; }
virtual String ToString() override;