diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-06-04 11:53:13 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-04 11:53:13 -0700 |
| commit | f3d637ba4d90bc2e23db07f1a9df5a6be7533f08 (patch) | |
| tree | 3dfe2fd73309ed4caf4ad6d4e8ee7a138296dfc3 /source/slang | |
| parent | 1b8731c809761c4e2dbec81dcee207f8a4621903 (diff) | |
First steps toward inheritance for struct types (#1366)
* First steps toward inheritance for struct types
This change adds the ability for a `struct` type to declare a base type that is another `struct`:
```hlsl
struct Base
{
int baseMember;
}
struct Derived : Base
{
int derivedMember;
}
```
The semantics of the feature are that code like the above desugars into code like:
```hlsl
struct Base
{
int baseMember;
}
struct Derived
{
Base _base;
int derivedMember;
}
```
At points where a member from the base type is being projected out, or the value is being implicitly cast to the base type, the compiler transforms the code to reference the implicitly-generated `_base` member. That means code like this:
```hlsl
void f(Base b);
...
Derived d = ...;
int x = d.baseMember;
f(d);
```
gets transformed into a form like this:
```hlsl
void f(Base b);
...
Derived d = ...;
int x = d._base.baseMember;
f(d._base);
```
Note that as a result of this choice, the behavior when passing a `Derived` value to a function that expects a `Base` (including to inherited member functions) is that of "object shearing" from the C++ world: the called function can only "see" the `Base` part of the argument, and any operations performed on it will behave as if the value was indeed a `Base`. There is no polymorphism going on because Slang doesn't currently have `virtual` methods.
In an attempt to work toward inheritance being a robust feature, this change adds a bunch of more detailed logic for checking the bases of various declarations:
* An `interface` declaration is only allowed to inherit from other `interface`s
* An `extension` declaration can only introduce inheritance from `interface`s
* A `struct` declaration can only inherit from at most one other `struct`, and that `struct` must be the first entry in the list of bases
This change also adds a mechanism to control whether a `struct` or `interface` in one module can inherit from a `struct` or `interface` declared in another module:
* If the base declaration is marked `[open]`, then the inheritance is allowed
* If the base declaration is marked `[sealed]`, then the inheritance is allowed
* If it is not marked otherwise, a `struct` is implicitly `[sealed]`
* If it is not marked otherwise, an `interface` is implicitly `[open]`
These seem like reasonable defaults. In order to safeguard the standard library a bit, the interfaces for builtin types have been marked `[sealed]` to make sure that a user cannot declare a `struct` and then mark it as a `BuiltinFloatingPointType`. This step should bring us a bit closer to being able to document and expose these interfaces for built-in types so that users can write code that is generic over them.
There are some big caveats with this work, such that it really only represents a stepping-stone toward a usable inheritance feature. The most important caveats are:
* If a `Derived` type tries to conform to an interface, such that one or more interface requirements are satisfied with members inherited from the `Base` type, that is likely to cause a crash or incorrect code generation.
* If a `Derived` type tries to inherit from a `Base` type that conforms to one or more interfaces, the witness table generated for the conformance of `Derived` to that interface is likely to lead to a crash or incorrect code generation.
It is clear that solving both of those issues will be necessary before we can really promote `struct` inheritance as a feature for users to try out.
* fixup: trying to appease clang error
* fixups: review feedback
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/core.meta.slang | 14 | ||||
| -rw-r--r-- | source/slang/slang-ast-expr.h | 16 | ||||
| -rw-r--r-- | source/slang/slang-ast-modifier.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-check-conformance.cpp | 71 | ||||
| -rw-r--r-- | source/slang/slang-check-constraint.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-check-conversion.cpp | 10 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 377 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 9 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 60 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 16 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 168 |
11 files changed, 614 insertions, 141 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 2b43206a3..0c39f2c2f 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -18,9 +18,11 @@ syntax constexpr : ConstExprModifier; syntax globallycoherent : GloballyCoherentModifier; // A type that can be used as an operand for builtins +[sealed] interface __BuiltinType {} // A type that can be used for arithmetic operations +[sealed] interface __BuiltinArithmeticType : __BuiltinType { /// Initialize from a 32-bit signed integer value. @@ -28,19 +30,24 @@ interface __BuiltinArithmeticType : __BuiltinType } /// A type that can be used for logical/bitwsie operations +[sealed] interface __BuiltinLogicalType : __BuiltinType {} // A type that logically has a sign (positive/negative/zero) +[sealed] interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {} // A type that can represent integers +[sealed] interface __BuiltinIntegerType : __BuiltinArithmeticType {} // A type that can represent non-integers +[sealed] interface __BuiltinRealType : __BuiltinSignedArithmeticType {} // A type that uses a floating-point representation +[sealed] interface __BuiltinFloatingPointType : __BuiltinRealType { /// Initialize from a 32-bit floating-point value. @@ -1917,3 +1924,10 @@ attribute_syntax [__extern] : ExternAttribute; __attributeTarget(FunctionDeclBase) attribute_syntax [__unsafeForceInlineEarly] : UnsafeForceInlineEarlyAttribute; + +// Inheritance Control +__attributeTarget(AggTypeDecl) +attribute_syntax [sealed] : SealedAttribute; + +__attributeTarget(AggTypeDecl) +attribute_syntax [open] : OpenAttribute; diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index e7111e631..96e90ae02 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -228,15 +228,21 @@ class ImplicitCastExpr : public TypeCastExpr SLANG_CLASS(ImplicitCastExpr) }; - /// A cast from a value to an interface ("existential") type. -class CastToInterfaceExpr: public Expr + /// A cast of a value to a super-type of its type. + /// + /// The type being cast to is stored as this expression's `type`. + /// +class CastToSuperTypeExpr: public Expr { - SLANG_CLASS(CastToInterfaceExpr) + SLANG_CLASS(CastToSuperTypeExpr) - /// The value being cast to an interface type + /// The value being cast to a super type + /// + /// The type being case from is `valueArg->type`. + /// RefPtr<Expr> valueArg; - /// A witness showing that `valueArg` conforms to the chosen interface + /// A witness showing that `valueArg`'s type is a sub-type of this expression's `type` RefPtr<Val> witnessArg; }; diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h index 065118c7a..4badbd02a 100644 --- a/source/slang/slang-ast-modifier.h +++ b/source/slang/slang-ast-modifier.h @@ -873,4 +873,14 @@ class UnsafeForceInlineEarlyAttribute : public Attribute SLANG_CLASS(UnsafeForceInlineEarlyAttribute) }; + /// An attribute that marks a type declaration as either allowing or + /// disallowing the type to be inherited from in other modules. +class InheritanceControlAttribute : public Attribute { SLANG_CLASS(InheritanceControlAttribute) }; + + /// An attribute that marks a type declaration as allowing the type to be inherited from in other modules. +class OpenAttribute : public InheritanceControlAttribute { SLANG_CLASS(OpenAttribute) }; + + /// An attribute that marks a type declaration as disallowing the type to be inherited from in other modules. +class SealedAttribute : public InheritanceControlAttribute { SLANG_CLASS(SealedAttribute) }; + } // namespace Slang diff --git a/source/slang/slang-check-conformance.cpp b/source/slang/slang-check-conformance.cpp index 4d54a93fb..f57e7cb38 100644 --- a/source/slang/slang-check-conformance.cpp +++ b/source/slang/slang-check-conformance.cpp @@ -18,15 +18,15 @@ namespace Slang } RefPtr<Val> SemanticsVisitor::createTypeWitness( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef, + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef, TypeWitnessBreadcrumb* inBreadcrumbs) { if(!inBreadcrumbs) { // We need to construct a witness to the fact - // that `type` has been proven to be *equal* - // to `interfaceDeclRef`. + // that `subType` has been proven to be *equal* + // to `superTypeDeclRef`. // SLANG_UNEXPECTED("reflexive type witness"); UNREACHABLE_RETURN(nullptr); @@ -145,15 +145,15 @@ namespace Slang } } - bool SemanticsVisitor::doesTypeConformToInterfaceImpl( - RefPtr<Type> originalType, - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef, + bool SemanticsVisitor::_isDeclaredSubtype( + RefPtr<Type> originalSubType, + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef, RefPtr<Val>* outWitness, TypeWitnessBreadcrumb* inBreadcrumbs) { // for now look up a conformance member... - if(auto declRefType = as<DeclRefType>(type)) + if(auto declRefType = as<DeclRefType>(subType)) { auto declRef = declRefType->declRef; @@ -162,11 +162,11 @@ namespace Slang // TODO: This is actually a bit more complicated, as // the interface needs to be "object-safe" for us to // really make this determination... - if(declRef == interfaceDeclRef) + if(declRef == superTypeDeclRef) { if(outWitness) { - *outWitness = createTypeWitness(originalType, interfaceDeclRef, inBreadcrumbs); + *outWitness = createTypeWitness(originalSubType, superTypeDeclRef, inBreadcrumbs); } return true; } @@ -198,11 +198,11 @@ namespace Slang TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; - breadcrumb.sub = type; + breadcrumb.sub = subType; breadcrumb.sup = inheritedType; breadcrumb.declRef = inheritanceDeclRef; - if(doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb)) + if(_isDeclaredSubtype(originalSubType, inheritedType, superTypeDeclRef, outWitness, &breadcrumb)) { return true; } @@ -214,10 +214,10 @@ namespace Slang auto inheritedType = getSup(m_astBuilder, genConstraintDeclRef); TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; - breadcrumb.sub = type; + breadcrumb.sub = subType; breadcrumb.sup = inheritedType; breadcrumb.declRef = genConstraintDeclRef; - if (doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb)) + if (_isDeclaredSubtype(originalSubType, inheritedType, superTypeDeclRef, outWitness, &breadcrumb)) { return true; } @@ -252,14 +252,14 @@ namespace Slang breadcrumb.sup = sup; breadcrumb.declRef = constraintDeclRef; - if(doesTypeConformToInterfaceImpl(originalType, sup, interfaceDeclRef, outWitness, &breadcrumb)) + if(_isDeclaredSubtype(originalSubType, sup, superTypeDeclRef, outWitness, &breadcrumb)) { return true; } } } } - else if(auto taggedUnionType = as<TaggedUnionType>(type)) + else if(auto taggedUnionType = as<TaggedUnionType>(subType)) { // A tagged union type conforms to an interface if all of // the constituent types in the tagged union conform. @@ -276,10 +276,10 @@ namespace Slang { RefPtr<Val> caseWitness; - if(!doesTypeConformToInterfaceImpl( + if(!_isDeclaredSubtype( caseType, caseType, - interfaceDeclRef, + superTypeDeclRef, outWitness ? &caseWitness : nullptr, nullptr)) { @@ -304,8 +304,11 @@ namespace Slang // We will start out being conservative about what we accept // here, just to keep things simple. // - if(!isInterfaceSafeForTaggedUnion(interfaceDeclRef)) - return false; + if( auto superInterfaceDeclRef = superTypeDeclRef.as<InterfaceDecl>() ) + { + if(!isInterfaceSafeForTaggedUnion(superInterfaceDeclRef)) + return false; + } // If we reach this point then we have a concrete // witness for each of the case types, and that is @@ -315,7 +318,7 @@ namespace Slang { RefPtr<TaggedUnionSubtypeWitness> taggedUnionWitness = m_astBuilder->create<TaggedUnionSubtypeWitness>(); taggedUnionWitness->sub = taggedUnionType; - taggedUnionWitness->sup = DeclRefType::create(m_astBuilder, interfaceDeclRef); + taggedUnionWitness->sup = DeclRefType::create(m_astBuilder, superTypeDeclRef); taggedUnionWitness->caseWitnesses.swapWith(caseWitnesses); *outWitness = taggedUnionWitness; @@ -327,22 +330,30 @@ namespace Slang return false; } - bool SemanticsVisitor::DoesTypeConformToInterface( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef) + bool SemanticsVisitor::isDeclaredSubtype( + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef) { - return doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, nullptr, nullptr); + return _isDeclaredSubtype(subType, subType, superTypeDeclRef, nullptr, nullptr); } - RefPtr<Val> SemanticsVisitor::tryGetInterfaceConformanceWitness( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef) + RefPtr<Val> SemanticsVisitor::tryGetSubtypeWitness( + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef) { RefPtr<Val> result; - doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, &result, nullptr); + _isDeclaredSubtype(subType, subType, superTypeDeclRef, &result, nullptr); return result; } + + RefPtr<Val> SemanticsVisitor::tryGetInterfaceConformanceWitness( + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + return tryGetSubtypeWitness(type, interfaceDeclRef); + } + RefPtr<Val> SemanticsVisitor::createTypeEqualityWitness( Type* type) { diff --git a/source/slang/slang-check-constraint.cpp b/source/slang/slang-check-constraint.cpp index 427ba9ec2..c37af8892 100644 --- a/source/slang/slang-check-constraint.cpp +++ b/source/slang/slang-check-constraint.cpp @@ -80,7 +80,7 @@ namespace Slang DeclRef<InterfaceDecl> interfaceDeclRef) { // The most basic test here should be: does the type declare conformance to the trait. - if(DoesTypeConformToInterface(type, interfaceDeclRef)) + if(isDeclaredSubtype(type, interfaceDeclRef)) return type; // Just because `type` doesn't conform to the given `interfaceDeclRef`, that @@ -119,7 +119,7 @@ namespace Slang continue; // We only want to consider types that implement the target interface. - if(!DoesTypeConformToInterface(candidateType, interfaceDeclRef)) + if(!isDeclaredSubtype(candidateType, interfaceDeclRef)) continue; // We only want to consider types where we can implicitly convert from `type` diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index 1f34e240d..c4e809025 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -544,12 +544,12 @@ namespace Slang if (auto toDeclRefType = as<DeclRefType>(toType)) { auto toTypeDeclRef = toDeclRefType->declRef; - if (auto interfaceDeclRef = toTypeDeclRef.as<InterfaceDecl>()) + if (auto toAggTypeDeclRef = toTypeDeclRef.as<AggTypeDecl>()) { - if(auto witness = tryGetInterfaceConformanceWitness(fromType, interfaceDeclRef)) + if(auto witness = tryGetSubtypeWitness(fromType, toAggTypeDeclRef)) { if (outToExpr) - *outToExpr = createCastToInterfaceExpr(toType, fromExpr, witness); + *outToExpr = createCastToSuperTypeExpr(toType, fromExpr, witness); if (outCost) *outCost = kConversionCost_CastToInterface; return true; @@ -835,12 +835,12 @@ namespace Slang return castExpr; } - RefPtr<Expr> SemanticsVisitor::createCastToInterfaceExpr( + RefPtr<Expr> SemanticsVisitor::createCastToSuperTypeExpr( RefPtr<Type> toType, RefPtr<Expr> fromExpr, RefPtr<Val> witness) { - RefPtr<CastToInterfaceExpr> expr = m_astBuilder->create<CastToInterfaceExpr>(); + RefPtr<CastToSuperTypeExpr> expr = m_astBuilder->create<CastToSuperTypeExpr>(); expr->loc = fromExpr->loc; expr->type = QualType(toType); expr->valueArg = fromExpr; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 66a8ceaf8..8486bf107 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -117,10 +117,24 @@ namespace Slang void visitInheritanceDecl(InheritanceDecl* inheritanceDecl); - void visitAggTypeDecl(AggTypeDecl* decl); + /// Validate that `decl` isn't illegally inheriting from a type in another module. + /// + /// This call checks a single `inheritanceDecl` to make sure that it either + /// * names a base type from the same module as `decl`, or + /// * names a type that allows cross-module inheritance + void _validateCrossModuleInheritance( + AggTypeDeclBase* decl, + InheritanceDecl* inheritanceDecl); + + void visitInterfaceDecl(InterfaceDecl* decl); + + void visitStructDecl(StructDecl* decl); void visitEnumDecl(EnumDecl* decl); + /// Validate that the target type of an extension `decl` is valid. + void _validateExtensionDeclTargetType(ExtensionDecl* decl); + void visitExtensionDecl(ExtensionDecl* decl); }; @@ -984,25 +998,10 @@ namespace Slang base = TranslateTypeNode(base); inheritanceDecl->base = base; - // For now we only allow inheritance from interfaces, so - // we will validate that the type expression names an interface - - if(auto declRefType = as<DeclRefType>(base.type)) - { - if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>()) - { - return; - } - } - else if(base.type.is<ErrorType>()) - { - // If an error was already produced, don't emit a cascading error. - return; - } - - // If type expression didn't name an interface, we'll emit an error here - // TODO: deal with the case of an error in the type expression (don't cascade) - getSink()->diagnose( base.exp, Diagnostics::expectedAnInterfaceGot, base.type); + // Note: we do not check whether the type being inherited from + // is valid to use for inheritance here, because there could + // be contextual factors that need to be taken into account + // based on the declaration that is doing the inheriting. } // Concretize interface conformances so that we have witnesses as required for lookup. @@ -1660,6 +1659,12 @@ namespace Slang inheritanceDecl, baseInterfaceDeclRef); } + else if( auto structDeclRef = baseTypeDeclRef.as<StructDecl>() ) + { + // The type is saying it inherits from a `struct`, + // which doesn't require any checking at present + return nullptr; + } } getSink()->diagnose(inheritanceDecl, Diagnostics::unimplemented, "type not supported for inheritance"); @@ -1761,15 +1766,200 @@ namespace Slang } } - void SemanticsDeclBasesVisitor::visitAggTypeDecl(AggTypeDecl* decl) + void SemanticsDeclBasesVisitor::_validateCrossModuleInheritance( + AggTypeDeclBase* decl, + InheritanceDecl* inheritanceDecl) { - // TODO: We need to enumerate the bases here, - // and ideally form a "class precedence list" - // from them. + // Within a single module, users should be allowed to inherit + // one type from another more or less freely, so long as they + // don't violate fundamental validity conditions around + // inheritance. + // + // When an inheritance relationship is declared in one module, + // and the base type is in another module, we may want to + // enforce more restrictions. As a strong example, we probably + // don't want people to declare their own subtype of `int` + // or `Texture2D<float4>`. + // + // We start by checking if the type being inherited from is + // a decl-ref type, since that means it refers to a declaration + // that can be localized to its original module. + // + auto baseType = inheritanceDecl->base.type; + auto baseDeclRefType = as<DeclRefType>(baseType); + if( !baseDeclRefType ) + { + return; + } + auto baseDecl = baseDeclRefType->declRef.decl; + + // Using the parent/child hierarchy baked into `Decl`s we + // can find the modules that contain both the `decl` doing + // the inheriting, and the `baseDeclRefType` that is being + // inherited from. + // + // If those modules are the same, then we aren't seeing any + // kind of cross-module inheritance here, and there is nothing + // that needs enforcing. + // + auto moduleWithInheritance = getModule(decl); + auto moduleWithBaseType = getModule(baseDecl); + if( moduleWithInheritance == moduleWithBaseType ) + { + return; + } + + if( baseDecl->hasModifier<SealedAttribute>() ) + { + // If the original declaration had the `[sealed]` attribute on it, + // then it explicitly does *not* allow inheritance from other + // modules. + // + getSink()->diagnose(inheritanceDecl, Diagnostics::cannotInheritFromExplicitlySealedDeclarationInAnotherModule, baseType, moduleWithBaseType->getModuleDecl()->getName()); + return; + } + else if( baseDecl->hasModifier<OpenAttribute>() ) + { + // Conversely, if the original declaration had the `[open]` attribute + // on it, then it explicit *does* allow inheritance from other + // modules. + // + // In this case we don't need to check anything: the inheritance + // is allowed. + } + else if( as<InterfaceDecl>(baseDecl) ) + { + // If an interface isn't explicitly marked `[open]` or `[sealed]`, + // then the default behavior is to treat it as `[open]`, since + // interfaces are most often used to define protocols that + // users of a module can opt into. + } + else + { + // For any non-interface type, if the declaration didn't specify + // `[open]` or `[sealed]` then we assume `[sealed]` is the default. + // + getSink()->diagnose(inheritanceDecl, Diagnostics::cannotInheritFromImplicitlySealedDeclarationInAnotherModule, baseType, moduleWithBaseType->getModuleDecl()->getName()); + return; + } + } + void SemanticsDeclBasesVisitor::visitInterfaceDecl(InterfaceDecl* decl) + { for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) { ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); + auto baseType = inheritanceDecl->base.type; + + // It is possible that there was an error in checking the base type + // expression, and in such a case we shouldn't emit a cascading error. + // + if( auto baseErrorType = as<ErrorType>(baseType) ) + { + continue; + } + + // An `interface` type can only inherit from other `interface` types. + // + // TODO: In the long run it might make sense for an interface to support + // an inheritance clause naming a non-interface type, with the meaning + // that any type that implements the interface must be a sub-type of the + // type named in the inheritance clause. + // + auto baseDeclRefType = as<DeclRefType>(baseType); + if( !baseDeclRefType ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfInterfaceMustBeInterface, decl, baseType); + continue; + } + + auto baseDeclRef = baseDeclRefType->declRef; + auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>(); + if( !baseInterfaceDeclRef ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfInterfaceMustBeInterface, decl, baseType); + continue; + } + + // TODO: At this point we have the `baseInterfaceDeclRef` + // and could use it to perform further validity checks, + // and/or to build up a more refined representation of + // the inheritance graph for this type (e.g., a "class + // precedence list"). + // + // E.g., we can/should check that we aren't introducing + // a circular inheritance relationship. + + _validateCrossModuleInheritance(decl, inheritanceDecl); + } + } + + void SemanticsDeclBasesVisitor::visitStructDecl(StructDecl* decl) + { + // A `struct` type can only inherit from `struct` or `interface` types. + // + // Furthermore, only the first inheritance clause (in source + // order) is allowed to declare a base `struct` type. + // + Index inheritanceClauseCounter = 0; + for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) + { + Index inheritanceClauseIndex = inheritanceClauseCounter++; + + ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); + auto baseType = inheritanceDecl->base.type; + + // It is possible that there was an error in checking the base type + // expression, and in such a case we shouldn't emit a cascading error. + // + if( auto baseErrorType = as<ErrorType>(baseType) ) + { + continue; + } + + auto baseDeclRefType = as<DeclRefType>(baseType); + if( !baseDeclRefType ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfStructMustBeStructOrInterface, decl, baseType); + continue; + } + + auto baseDeclRef = baseDeclRefType->declRef; + if( auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>() ) + { + } + else if( auto baseStructDeclRef = baseDeclRef.as<StructDecl>() ) + { + // To simplify the task of reading and maintaining code, + // we require that when a `struct` inherits from another + // `struct`, the base `struct` is the first item in + // the list of bases (before any interfaces). + // + // This constraint also has the secondary effect of restricting + // it so that a `struct` cannot multiply inherit from other + // `struct` types. + // + if( inheritanceClauseIndex != 0 ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseStructMustBeListedFirst, decl, baseType); + } + } + else + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfStructMustBeStructOrInterface, decl, baseType); + continue; + } + + // TODO: At this point we have the `baseDeclRef` + // and could use it to perform further validity checks, + // and/or to build up a more refined representation of + // the inheritance graph for this type (e.g., a "class + // precedence list"). + // + // E.g., we can/should check that we aren't introducing + // a circular inheritance relationship. + + _validateCrossModuleInheritance(decl, inheritanceDecl); } } @@ -1795,46 +1985,74 @@ namespace Slang void SemanticsDeclBasesVisitor::visitEnumDecl(EnumDecl* decl) { - // 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. + // An `enum` type can inherit from interfaces, and also + // from a single "tag" type that must: + // + // * be a built-in integer type + // * come first in the list of base types + // + Index inheritanceClauseCounter = 0; RefPtr<Type> tagType; InheritanceDecl* tagTypeInheritanceDecl = nullptr; for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) { + Index inheritanceClauseIndex = inheritanceClauseCounter++; + ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); + auto baseType = inheritanceDecl->base.type; - // Look at the type being inherited from. - auto superType = inheritanceDecl->base.type; + // It is possible that there was an error in checking the base type + // expression, and in such a case we shouldn't emit a cascading error. + // + if( auto baseErrorType = as<ErrorType>(baseType) ) + { + continue; + } - if(auto errorType = as<ErrorType>(superType)) + auto baseDeclRefType = as<DeclRefType>(baseType); + if( !baseDeclRefType ) { - // Ignore any erroneous inheritance clauses. + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfEnumMustBeIntegerOrInterface, decl, baseType); continue; } - else if(auto declRefType = as<DeclRefType>(superType)) + + auto baseDeclRef = baseDeclRefType->declRef; + if( auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>() ) + { + _validateCrossModuleInheritance(decl, inheritanceDecl); + } + else if( auto baseStructDeclRef = baseDeclRef.as<StructDecl>() ) { - if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>()) + // To simplify the task of reading and maintaining code, + // we require that when an `enum` declares an explicit + // underlying tag type using an inheritance clause, that + // type must be the first item in the list of bases. + // + // This constraint also has the secondary effect of restricting + // it so that an `enum` can't possibly have multiple tag + // types declared. + // + if( inheritanceClauseIndex != 0 ) { - // Don't consider interface bases as candidates for - // the tag type. - continue; + getSink()->diagnose(inheritanceDecl, Diagnostics::tagTypeMustBeListedFirst, decl, baseType); + } + else + { + tagType = baseType; + tagTypeInheritanceDecl = inheritanceDecl; } - } - if(tagType) - { - // We already found a tag type. - getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType); - getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType); - break; + // Note: we do *not* apply the code that validates + // cross-module inheritance to a base that represnts + // a tag type, because declaring a tag type for an + // `enum` doesn't actually make it into a subtype + // of the tag type, and thus doesn't violate the + // rules when the tag type is `sealed`. } else { - tagType = superType; - tagTypeInheritanceDecl = inheritanceDecl; + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfEnumMustBeIntegerOrInterface, decl, baseType); + continue; } } @@ -1845,6 +2063,7 @@ namespace Slang // `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 = m_astBuilder->getIntType(); @@ -2852,10 +3071,8 @@ namespace Slang } } - void SemanticsDeclBasesVisitor::visitExtensionDecl(ExtensionDecl* decl) + void SemanticsDeclBasesVisitor::_validateExtensionDeclTargetType(ExtensionDecl* decl) { - decl->targetType = CheckProperType(decl->targetType); - if (auto targetDeclRefType = as<DeclRefType>(decl->targetType)) { // Attach our extension to that type as a candidate... @@ -2867,7 +3084,65 @@ namespace Slang return; } } - getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here"); + getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "an 'extension' can only extend a nominal type"); + } + + void SemanticsDeclBasesVisitor::visitExtensionDecl(ExtensionDecl* decl) + { + // We check the target type expression, and then validate + // that the type it names is one that it makes sense + // to extend. + // + decl->targetType = CheckProperType(decl->targetType); + _validateExtensionDeclTargetType(decl); + + for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) + { + ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); + auto baseType = inheritanceDecl->base.type; + + // It is possible that there was an error in checking the base type + // expression, and in such a case we shouldn't emit a cascading error. + // + if( auto baseErrorType = as<ErrorType>(baseType) ) + { + continue; + } + + // An `extension` can only introduce inheritance from `interface` types. + // + // TODO: It might in theory make sense to allow an `extension` to + // introduce a non-`interface` base if we decide that an `extension` + // within the same module as the type it extends counts as just + // a continuation of the type's body (like a `partial class` in C#). + // + auto baseDeclRefType = as<DeclRefType>(baseType); + if( !baseDeclRefType ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfExtensionMustBeInterface, decl, baseType); + continue; + } + + auto baseDeclRef = baseDeclRefType->declRef; + auto baseInterfaceDeclRef = baseDeclRef.as<InterfaceDecl>(); + if( !baseInterfaceDeclRef ) + { + getSink()->diagnose(inheritanceDecl, Diagnostics::baseOfExtensionMustBeInterface, decl, baseType); + continue; + } + + // TODO: At this point we have the `baseInterfaceDeclRef` + // and could use it to perform further validity checks, + // and/or to build up a more refined representation of + // the inheritance graph for this extension (e.g., a "class + // precedence list"). + // + // E.g., we can/should check that we aren't introducing + // an inheritance relationship that already existed + // on the type as originally declared. + + _validateCrossModuleInheritance(decl, inheritanceDecl); + } } RefPtr<Type> SemanticsVisitor::calcThisType(DeclRef<Decl> declRef) diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index d8eef571e..c0beb8262 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -325,10 +325,17 @@ namespace Slang { // TODO: do we need to make something more // explicit here? - bb = ConstructDeclRefExpr( + auto expr = ConstructDeclRefExpr( breadcrumb->declRef, bb, loc); + + if(bb && bb->type.isLeftValue) + { + expr->type.isLeftValue = true; + } + + bb = expr; } break; diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 42edb5df7..c316dd820 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -645,7 +645,7 @@ namespace Slang /// which packages up the value, its type, and the witness /// of its conformance to the interface. /// - RefPtr<Expr> createCastToInterfaceExpr( + RefPtr<Expr> createCastToSuperTypeExpr( RefPtr<Type> toType, RefPtr<Expr> fromExpr, RefPtr<Val> witness); @@ -924,9 +924,15 @@ namespace Slang RefPtr<DeclaredSubtypeWitness> createSimpleSubtypeWitness( TypeWitnessBreadcrumb* breadcrumb); + /// Create a withness that `subType` is a sub-type of `superTypeDeclRef`. + /// + /// The `inBreadcrumbs` parameter represents a linked list of steps + /// in the process that validated the sub-type relationship, which + /// will be used to inform the construction of the witness. + /// RefPtr<Val> createTypeWitness( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef, + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef, TypeWitnessBreadcrumb* inBreadcrumbs); /// Is the given interface one that a tagged-union type can conform to? @@ -950,20 +956,48 @@ namespace Slang DeclRef<InterfaceDecl> interfaceDeclRef, DeclRef<Decl> requirementDeclRef); - bool doesTypeConformToInterfaceImpl( - RefPtr<Type> originalType, - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef, + /// Check whether `subType` is declared a sub-type of `superTypeDeclRef` + /// + /// If this function returns `true` (because the subtype relationship holds), + /// then `outWitness` will be set to a value that serves as a witness + /// to the subtype relationship. + /// + /// This function may be used to validate a transitive subtype relationship + /// where, e.g., `A : C` becase `A : B` and `B : C`. In such a case, a recursive + /// call to `_isDeclaredSubtype` may occur where `originalSubType` is `A`, + /// `subType` is `C`, and `superTypeDeclRef` is `C`. The `inBreadcrumbs` in that + /// case would include information for the `A : B` relationship, which can be + /// used to construct a witness for `A : C` from the `A : B` and `B : C` witnesses. + /// + bool _isDeclaredSubtype( + RefPtr<Type> originalSubType, + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef, RefPtr<Val>* outWitness, TypeWitnessBreadcrumb* inBreadcrumbs); - bool DoesTypeConformToInterface( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef); + /// Check whether `subType` is a sub-type of `superTypeDeclRef`. + bool isDeclaredSubtype( + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef); + + /// Check whether `subType` is a sub-type of `superTypeDeclRef`, + /// and return a witness to the sub-type relationship if it holds + /// (return null otherwise). + /// + RefPtr<Val> tryGetSubtypeWitness( + RefPtr<Type> subType, + DeclRef<AggTypeDecl> superTypeDeclRef); + /// Check whether `type` conforms to `interfaceDeclRef`, + /// and return a witness to the conformance if it holds + /// (return null otherwise). + /// + /// This function is equivalent to `tryGetSubtypeWitness()`. + /// RefPtr<Val> tryGetInterfaceConformanceWitness( - RefPtr<Type> type, - DeclRef<InterfaceDecl> interfaceDeclRef); + RefPtr<Type> type, + DeclRef<InterfaceDecl> interfaceDeclRef); /// Does there exist an implicit conversion from `fromType` to `toType`? bool canConvertImplicitly( @@ -1381,7 +1415,7 @@ namespace Slang CASE(OverloadedExpr) CASE(OverloadedExpr2) CASE(AggTypeCtorExpr) - CASE(CastToInterfaceExpr) + CASE(CastToSuperTypeExpr) CASE(LetExpr) CASE(ExtractExistentialValueExpr) diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 6e77aa45d..41c674ddb 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -289,8 +289,6 @@ DIAGNOSTIC(31120, Error, invalidAttributeTarget, "invalid syntax target for user // 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") @@ -320,6 +318,18 @@ DIAGNOSTIC(30610, Error, ambiguousDefaultInitializerForType, "more than one defa // 307xx: parameters DIAGNOSTIC(30700, Error, outputParameterCannotHaveDefaultValue, "an 'out' or 'inout' parameter cannot have a default-value expression"); +// 308xx: inheritance +DIAGNOSTIC(30810, Error, baseOfInterfaceMustBeInterface, "interface '$0' cannot inherit from non-interface type '$1'") +DIAGNOSTIC(30811, Error, baseOfStructMustBeStructOrInterface, "struct '$0' cannot inherit from type '$1' that is neither a struct nor an interface") +DIAGNOSTIC(30812, Error, baseOfEnumMustBeIntegerOrInterface, "enum '$0' cannot inherit from type '$1' that is neither an interface not a builtin integer type") +DIAGNOSTIC(30810, Error, baseOfExtensionMustBeInterface, "extension cannot inherit from non-interface type '$1'") + +DIAGNOSTIC(30820, Error, baseStructMustBeListedFirst, "a struct type may only inherit from one other struct type, and that type must appear first in the list of bases") +DIAGNOSTIC(30821, Error, tagTypeMustBeListedFirst, "an unum type may only have a single tag type, and that type must be listed first in the list of bases") + +DIAGNOSTIC(30820, Error, cannotInheritFromExplicitlySealedDeclarationInAnotherModule, "cannot inherit from type '$0' marked 'sealed' in module '$1'") +DIAGNOSTIC(30821, Error, cannotInheritFromImplicitlySealedDeclarationInAnotherModule, "cannot inherit from type '$0' in module '$1' because it is implicitly 'sealed'; mark the base type 'open' to allow inheritance across modules") + // 39999 waiting to be placed in the right range DIAGNOSTIC(39999, Error, expectedIntegerConstantWrongType, "expected integer constant (found: '$0')") @@ -343,8 +353,6 @@ DIAGNOSTIC(39999, Error, expectedAGeneric, "expected a generic when using '<...> DIAGNOSTIC(39999, Error, genericArgumentInferenceFailed, "could not specialize generic for arguments of type $0") DIAGNOSTIC(39999, Note, genericSignatureTried, "see declaration of $0") -DIAGNOSTIC(39999, Error, expectedAnInterfaceGot, "expected an interface, got '$0'") - DIAGNOSTIC(39999, Error, ambiguousReference, "ambiguous reference to '$0'"); DIAGNOSTIC(39999, Error, ambiguousExpression, "ambiguous reference"); diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index f369729d2..c52025244 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -701,17 +701,17 @@ LoweredValInfo emitCallToDeclRef( } IRInst* getFieldKey( - IRGenContext* context, - DeclRef<VarDecl> field) + IRGenContext* context, + DeclRef<Decl> field) { return getSimpleVal(context, emitDeclRef(context, field, context->irBuilder->getKeyType())); } LoweredValInfo extractField( - IRGenContext* context, - IRType* fieldType, - LoweredValInfo base, - DeclRef<VarDecl> field) + IRGenContext* context, + IRType* fieldType, + LoweredValInfo base, + DeclRef<Decl> field) { IRBuilder* builder = context->irBuilder; @@ -2054,6 +2054,21 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> } else if(auto constraintDeclRef = declRef.as<TypeConstraintDecl>()) { + auto superType = getSup(getASTBuilder(), constraintDeclRef); + if(auto superDeclRefType = as<DeclRefType>(superType)) + { + if(auto superStructDeclRef = superDeclRefType->declRef.template as<StructDecl>()) + { + // The constraint is saying that the given type inherits + // from a concrete `struct` type, which means it should + // be satisfied by a witness that represents a field + // (TODO: or a chain of fields) to fetch to get the + // final value. + // + return extractField(loweredType, loweredBase, constraintDeclRef); + } + } + // The code is making use of a "witness" that a value of // some generic type conforms to an interface. // @@ -2748,30 +2763,87 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> UNREACHABLE_RETURN(LoweredValInfo()); } - LoweredValInfo visitCastToInterfaceExpr( - CastToInterfaceExpr* expr) + /// Emit code to cast `value` to a concrete `superType` (e.g., a `struct`). + /// + /// The `subTypeWitness` is expected to witness the sub-type relationship + /// by naming a field (or chain of fields) that leads from the type of + /// `value` to the field that stores its members for `superType`. + /// + LoweredValInfo emitCastToConcreteSuperTypeRec( + LoweredValInfo const& value, + IRType* superType, + Val* subTypeWitness) { - // We have an expression that is "up-casting" some concrete value - // to an existential type (aka interface type), using a subtype witness - // (which will lower as a witness table) to show that the conversion - // is valid. - // - // At the IR level, this will become a `makeExistential` instruction, - // which collects the above information into a single IR-level value. - // A dynamic CPU implementation of Slang might encode an existential - // as a "fat pointer" representation, which includes a pointer to - // data for the concrete value, plus a pointer to the witness table. + if( auto declaredSubtypeWitness = as<DeclaredSubtypeWitness>(subTypeWitness) ) + { + return extractField(superType, value, declaredSubtypeWitness->declRef); + } + else + { + SLANG_ASSERT(!"unhandled"); + return nullptr; + } + } + + LoweredValInfo visitCastToSuperTypeExpr( + CastToSuperTypeExpr* expr) + { + auto superType = lowerType(context, expr->type); + auto value = lowerRValueExpr(context, expr->valueArg); + + // The actual operation that we need to perform here + // depends on the kind of subtype relationship we + // are making use of. // - // Note: if/when Slang supports more general existential types, such - // as compositions of interface (e.g., `IReadable & IWritable`), then - // we should probably extend the AST and IR mechanism here to accept - // a sequence of witness tables. + // The first important case is when the super type is + // an interface type, such that casting from a concrete + // value to that type creates a value of existential + // type that binds together the concrete value and the + // witness table that represents the subtype relationship. // - auto existentialType = lowerType(context, expr->type); - auto concreteValue = getSimpleVal(context, lowerRValueExpr(context, expr->valueArg)); - auto witnessTable = lowerSimpleVal(context, expr->witnessArg); - auto existentialValue = getBuilder()->emitMakeExistential(existentialType, concreteValue, witnessTable); - return LoweredValInfo::simple(existentialValue); + if( auto declRefType = as<DeclRefType>(expr->type) ) + { + auto declRef = declRefType->declRef; + if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() ) + { + // We have an expression that is "up-casting" some concrete value + // to an existential type (aka interface type), using a subtype witness + // (which will lower as a witness table) to show that the conversion + // is valid. + // + auto witnessTable = lowerSimpleVal(context, expr->witnessArg); + + // At the IR level, this will become a `makeExistential` instruction, + // which collects the above information into a single IR-level value. + // A dynamic CPU implementation of Slang might encode an existential + // as a "fat pointer" representation, which includes a pointer to + // data for the concrete value, plus a pointer to the witness table. + // + // Note: if/when Slang supports more general existential types, such + // as compositions of interface (e.g., `IReadable & IWritable`), then + // we should probably extend the AST and IR mechanism here to accept + // a sequence of witness tables. + // + auto concreteValue = getSimpleVal(context, value); + auto existentialValue = getBuilder()->emitMakeExistential( + superType, + concreteValue, + witnessTable); + return LoweredValInfo::simple(existentialValue); + } + else if( auto structDeclRef = declRef.as<StructDecl>() ) + { + // We are up-casting to a concrete `struct` super-type, + // such that the witness will represent a field of the super-type + // that is stored in instances of the sub-type (or a chain + // of such fields for a transitive witness). + // + return emitCastToConcreteSuperTypeRec(value, superType, expr->witnessArg); + } + } + + SLANG_UNEXPECTED("unexpected case of subtype relationship"); + UNREACHABLE_RETURN(LoweredValInfo()); } LoweredValInfo subscriptValue( @@ -2815,9 +2887,9 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> } LoweredValInfo extractField( - IRType* fieldType, - LoweredValInfo base, - DeclRef<VarDecl> field) + IRType* fieldType, + LoweredValInfo base, + DeclRef<Decl> field) { return Slang::extractField(context, fieldType, base, field); } @@ -4524,6 +4596,21 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // What is the super-type that we have declared we inherit from? RefPtr<Type> superType = inheritanceDecl->base.type; + if(auto superDeclRefType = as<DeclRefType>(superType)) + { + if( auto superStructDeclRef = superDeclRefType->declRef.as<StructDecl>() ) + { + // TODO: the witness that a type inherits from a `struct` + // type should probably be a key that will be used for + // a field that holds the base type... + // + auto irKey = getBuilder()->createStructKey(); + auto keyVal = LoweredValInfo::simple(irKey); + setGlobalValue(context, inheritanceDecl, keyVal); + return keyVal; + } + } + // Construct the mangled name for the witness table, which depends // on the type that is conforming, and the type that it conforms to. // @@ -5270,6 +5357,27 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> subBuilder->setInsertInto(irStruct); + // A `struct` that inherits from another `struct` must start + // with a member for the direct base type. + // + for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) + { + auto superType = inheritanceDecl->base; + if(auto superDeclRefType = as<DeclRefType>(superType)) + { + if(auto superStructDeclRef = superDeclRefType->declRef.as<StructDecl>()) + { + auto superKey = (IRStructKey*) getSimpleVal(context, ensureDecl(context, inheritanceDecl)); + auto irSuperType = lowerType(context, superType.type); + subBuilder->createStructField( + irStruct, + superKey, + irSuperType); + } + } + } + + for (auto fieldDecl : decl->getMembersOfType<VarDeclBase>()) { if (fieldDecl->hasModifier<HLSLStaticModifier>()) |
