diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-06-12 14:59:13 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-06-12 14:59:13 -0700 |
| commit | 167d8579870db18756c234755b197e4ded930b0e (patch) | |
| tree | 942f07efe9572699a62df80cadb136a1789780a6 /source | |
| parent | 7852a2bf0ef5aad0f4f318507c300352c25199f2 (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')
| -rw-r--r-- | source/slang/check.cpp | 504 | ||||
| -rw-r--r-- | source/slang/compiler.h | 10 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 45 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 48 | ||||
| -rw-r--r-- | source/slang/decl-defs.h | 15 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 12 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 75 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 158 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 13 | ||||
| -rw-r--r-- | source/slang/type-defs.h | 9 |
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; |
