diff options
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; |
