From 0eddf45f36f3948c18b8a45ef52b980f2a2b0cc9 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Wed, 17 Jun 2020 14:55:46 -0700 Subject: Work on struct inheritance and interfaces The main new feature that works here is that a derived `struct` type can satisfy one or more interface requirements using methods it inherited from a base `struct` type: ```hlsl interface ICounter { [mutating] void increment(); } struct CounterBase { int val; [mutating] void increment() { val++; } } struct ResetableCounter : CounterBase, ICounter { [mutating] void reset() { val = 0; } } ``` Here the derived `ResetableCounter` type is satisfying the `increment()` requirement from `ICounter` using the inherited `CounterBase` method instead of one defined on `ResetableCounter`. The crux of the problem here was that after lowering to HLSL/GLSL, the above code looks something like: ```hlsl struct CounterBase { int val; }; void CounterBase_increment(in out CounterBase this) { this.val++; } struct ResetableCounter { CounterBase base; } void ResetableCounter_reset(in out ResetableCounter this) { this.base.val = 0; } ``` The central problem is that `CounterBase_increment` here is not type-compatible what we expect to find in the witness table for `ResetableCounter : ICounter`: the `this` parameter has the wrong type! The basic solution strategy here is to intercept the search for a witness to sastify an interface requirement in `findWitnessForInterfaceRequirement` (those witnesses get collected into a witness table). The revised logic first looks for an exact match, which will only consider members introduced for the type itself, and not those introduced by base types. If an exact match for a method requirement is not found, the semantic checker then tries to *synthesize* a witness for the requirement, which more or less amounts to generating a function like: ```hlsl [mutating] void ResetableCounter::synthesized_increment() { this.increment(); } ``` The body of that synthesized method will type-check just fine in this case (because it desugars into `this.base.increment()`, more or less), and thus the synthesized method declaration can be used as the actual witness that drives downstream code generation. Details: * I added some options to lookup to allow us to explicitly skip member lookup through base interfaces; this should make sure that we don't accidentally satisfy an interface requirement using a member of the same or another interface (since such members are conceptually `abstract`). * As it originally stood, the semantic checker was allowing `CounterBase.increment()` to satisfy the `increment()` requirement of `ResetableCounter` directly, with the result that we got invalid HLSL/GLSL code as output. In order to avoid this and other bad cases, I made sure that the "exact match" case of requirement satisfaction ignores members that included any "breadcrumbs" in the lookup result item (since the breadcrumbs would all indicate transformations that needed to be applied to `this` to find the right member). * If we eventually have targets where `this` is passed by pointer/reference in all cases, then all of this work is not needed for the common case of single inheritance, and the base-type method should be usable as a witness directly. I don't see any easy way to handle that special case without producing target-dependent code in the front-end. It might be that we need an IR pass that can detect functions that are trivial "forwarding" functions and replace them with the function they forward to. * This change includes a test case that should have come along with the original PR that started adding struct inheritance Caveats: * The comments in this change talk about things like allowing a method with a default parameter to satisfy a requirement without that parameter. That scenario won't actually work at present because we still have an enormous hack in our logic for checking methods against requirements: we don't actually consider their signatures! I couldn't fold a fix for that issue into this change because there are subtle corner cases around associated types that we need to handle correctly (which were part of the reason why the checking is as hacked as it is) * This change does *not* try to test or address the case where we want to have a `Derived` type conform to `ISomething` because it inherits from `Base` and `Base : ISomething`. That case has its own details that need to be worked out, but ideally can follow a similar implementation strategy when it comes to re-using methods from `Base` to satisfy requirement on `Derived`. --- source/slang/slang-ast-support-types.h | 8 + source/slang/slang-check-decl.cpp | 389 ++++++++++++++++++++- source/slang/slang-check-impl.h | 29 +- source/slang/slang-lookup.cpp | 15 +- source/slang/slang-lookup.h | 3 +- .../struct-inherit-interface-requirement.slang | 69 ++++ ...nherit-interface-requirement.slang.expected.txt | 0 .../inheritance/struct-inheritance.slang | 58 +++ .../struct-inheritance.slang.expected.txt | 4 + 9 files changed, 560 insertions(+), 15 deletions(-) create mode 100644 tests/language-feature/inheritance/struct-inherit-interface-requirement.slang create mode 100644 tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt create mode 100644 tests/language-feature/inheritance/struct-inheritance.slang create mode 100644 tests/language-feature/inheritance/struct-inheritance.slang.expected.txt diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index ef6589ca4..66144eaed 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -1016,6 +1016,13 @@ namespace Slang Default = type | Function | Value, }; + /// Flags for options to be used when looking up declarations + enum class LookupOptions : uint8_t + { + None = 0, + IgnoreBaseInterfaces = 1 << 0, + }; + // Represents one item found during lookup struct LookupResultItem { @@ -1224,6 +1231,7 @@ namespace Slang RefPtr endScope = nullptr; LookupMask mask = LookupMask::Default; + LookupOptions options = LookupOptions::None; }; struct WitnessTable; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index ab22108b9..ed273e2a4 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -1443,6 +1443,325 @@ namespace Slang return false; } + bool SemanticsVisitor::trySynthesizeMethodRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef requiredMemberDeclRef, + RefPtr witnessTable) + { + // The situation here is that the context of an inheritance + // declaration didn't provide an exact match for a required + // method. E.g.: + // + // interface ICounter { [mutating] int increment(); } + // struct MyCounter : ICounter + // { + // int increment(int val = 1) { ... } + // } + // + // It is clear in this case that the `MyCounter` type *can* + // satisfy the signature required by `ICounter`, but it has + // no explicit method declaration that is a perfect match. + // + // The approach in this function will be to construct a + // synthesized method along the lines of: + // + // struct MyCounter ... + // { + // ... + // int synthesized() + // { + // return this.increment(); + // } + // } + // + // That is, we construct a method with the exact signature + // of the requirement (same parameter and result types), + // and then provide it with a body that simple `return`s + // the result of applying the desired requirement name + // (`increment` in this case) to those parameters. + // + // If the synthesized method type-checks, then we can say + // that the type must satisfy the requirement structurally, + // even if there isn't an exact signature match. More + // importantly, the method we just synthesized can be + // used as a witness to the fact that the requirement is + // satisfied. + + // With the big picture spelled out, we can settle into + // the work of constructing our synthesized method. + // + auto synFuncDecl = m_astBuilder->create(); + + // For now our synthesized method will use the name and source + // location of the requirement we are trying to satisfy. + // + // TODO: as it stands right now our syntesized method will + // get a mangled name, which we don't actually want. Leaving + // out the name here doesn't help matters, because then *all* + // snthesized methods on a given type would share the same + // mangled name! + // + synFuncDecl->nameAndLoc = requiredMemberDeclRef.getDecl()->nameAndLoc; + + // The result type of our synthesized method will be the expected + // result type from the interface requirement. + // + // TODO: This logic can/will run into problems if the return type + // is an associated type. + // + // The ideal solution is that we should be solving for interface + // conformance in two phases: a first phase to solve for how + // associated types are satisfied, and then a second phase to solve + // for how other requirements are satisfied (where we can substitute + // in the associated type witnesses for the abstract associated + // types as part of `requiredMemberDeclRef`). + // + // TODO: We should also double-check that this logic will work + // with a method that returns `This`. + // + auto resultType = getResultType(m_astBuilder, requiredMemberDeclRef); + synFuncDecl->returnType.type = resultType; + + // Our synthesized method will have parameters matching the names + // and types of those on the requirement, and it will use expressions + // that reference those parametesr as arguments for the call expresison + // that makes up the body. + // + List synArgs; + for( auto paramDeclRef : getParameters(requiredMemberDeclRef) ) + { + auto paramType = getType(m_astBuilder, paramDeclRef); + + // For each parameter of the requirement, we create a matching + // parameter (same name and type) for the synthesized method. + // + auto synParamDecl = m_astBuilder->create(); + synParamDecl->nameAndLoc = paramDeclRef.getDecl()->nameAndLoc; + synParamDecl->type.type = resultType; + + // We need to add the parameter as a child declaration of + // the method we are building. + // + synParamDecl->parentDecl = synFuncDecl; + synFuncDecl->members.add(synParamDecl); + + // For each paramter, we will create an argument expression + // for the call in the function body. + // + auto synArg = m_astBuilder->create(); + synArg->declRef = makeDeclRef(synParamDecl); + synArg->type = paramType; + synArgs.add(synArg); + } + + // Required interface methods can be `static` or non-`static`, + // and non-`static` methods can be `[mutating]` or non-`[mutating]`. + // All of these details affect how we introduce our `this` parameter, + // if any. + // + ThisExpr* synThis = nullptr; + if( !requiredMemberDeclRef.getDecl()->hasModifier() ) + { + // For a non-`static` requirement, we need a `this` parameter. + // + synThis = m_astBuilder->create(); + + // The type of `this` in our method will be the type for + // which we are synthesizing a conformance. + // + synThis->type.type = context->conformingType; + + if( requiredMemberDeclRef.getDecl()->hasModifier() ) + { + // If the interface requirement is `[mutating]` then our + // synthesized method should be too, and also the `this` + // parameter should be an l-value. + // + synThis->type.isLeftValue = true; + + auto synMutatingAttr = m_astBuilder->create(); + synFuncDecl->modifiers.first = synMutatingAttr; + } + } + + // The body of our synthesized method is going to try to + // make a call using the name of the method requirement (e.g., + // the name `increment` in our example at the top of this function). + // + // The caller already passed in a `LookupResult` that represents + // an attempt to look up the given name in the type of `this`, + // and we really just need to wrap that result up as an overloaded + // expression. + // + auto synBase = m_astBuilder->create(); + synBase->lookupResult2 = lookupResult; + + // If `synThis` is non-null, then we will use it as the base of + // the overloaded expression, so that we have an overloaded + // member reference, and not just an overloaded reference to some + // static definitions. + // + synBase->base = synThis; + + // We now have the reference to the overload group we plan to call, + // and we already built up the argument list, so we can construct + // an `InvokeExpr` that represents the call we want to make. + // + auto synCall = m_astBuilder->create(); + synCall->functionExpr = synBase; + synCall->arguments = synArgs; + + // In order to know if our call is well-formed, we need to run + // the semantic checking logic for overload resolution. If it + // runs into an error, we don't want that being reported back + // to the user as some kind of overload-resolution failure. + // + // In order to protect the user from whatever errors might + // occur, we will swap out the current diagnostic sink for + // a temporary one. + // + DiagnosticSink* savedSink = m_shared->m_sink; + DiagnosticSink tempSink(savedSink->getSourceManager()); + m_shared->m_sink = &tempSink; + + // With our temporary diagnostic sink soaking up any messages + // from overload resolution, we can now try to resolve + // the call to see what happens. + // + auto checkedCall = ResolveInvoke(synCall); + + // Of course, it is possible that the call went through fine, + // but the result isn't of the type we expect/require, + // so we also need to coerce the result of the call to + // the expected type. + // + auto coercedCall = coerce(resultType, checkedCall); + + // Once we are done making our semantic checks, we can + // restore the original sink, so that subsequent operations + // report diagnostics as usual. + // + m_shared->m_sink = savedSink; + + // If our overload resolution or type coercion failed, + // then we have not been able to synthesize a witness + // for the requirement. + // + // TODO: We might want to detect *why* overload resolution + // or type coercion failed, and report errors accordingly. + // + // More detailed diagnostics could help users understand + // what they did wrong, e.g.: + // + // * "We tried to use `foo(int)` but the interface requires `foo(String)` + // + // * "You have two methods that can apply as `bar()` and we couldn't tell which one you meant + // + // For now we just bail out here and rely on the caller to + // diagnose a generic "failed to satisfying requirement" error. + // + if(tempSink.getErrorCount() != 0) + return false; + + // If we were able to type-check the call, then we should + // be able to finish construction of a suitable witness. + // + // We've already created the outer declaration (including its + // parameters), and the inner expression, so the main work + // that is left is defining the body of the new function, + // which comprises a single `return` statement. + // + auto synReturn = m_astBuilder->create(); + synReturn->expression = coercedCall; + + synFuncDecl->body = synReturn; + + // Once we are sure that we want to use the declaration + // we've synthesized, aew can go ahead and wire it up + // to the AST so that subsequent stages can generate + // IR code from it. + // + // Note: we set the parent of the synthesized declaration + // to the parent of the inheritance declaration being + // validated (which is either a type declaration or + // an `extension`), but we do *not* add the syntehsized + // declaration to the list of child declarations at + // this point. + // + // By leaving the synthesized declaration off of the list + // of members, we ensure that it doesn't get found + // by lookup (e.g., in a module that `import`s this type). + // Unfortunately, we may also break invariants in other parts + // of the code if they assume that all declarations have + // to appear in the parent/child hierarchy of the module. + // + // TODO: We may need to properly wire the synthesized + // declaration into the hierarchy, but then attach a modifier + // to it to indicate that it should be ignored by things like lookup. + // + synFuncDecl->parentDecl = context->parentDecl; + + // Once our synthesized declaration is complete, we need + // to install it as the witness that satifies the given + // requirement. + // + // Subsequent code generation should not be able to tell the + // difference between our synthetic method and a hand-written + // one with the same behavior. + // + witnessTable->requirementDictionary.Add(requiredMemberDeclRef, + RequirementWitness(makeDeclRef(synFuncDecl))); + return true; + } + + bool SemanticsVisitor::trySynthesizeRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef requiredMemberDeclRef, + RefPtr witnessTable) + { + SLANG_UNUSED(lookupResult); + SLANG_UNUSED(requiredMemberDeclRef); + SLANG_UNUSED(witnessTable); + + if (auto requiredFuncDeclRef = requiredMemberDeclRef.as()) + { + // Check signature match. + return trySynthesizeMethodRequirementWitness( + context, + lookupResult, + requiredFuncDeclRef, + witnessTable); + } + + // TODO: There are other kinds of requirements for which synthesis should + // be possible: + // + // * It should be possible to synthesize required initializers + // using an approach similar to what is used for methods. + // + // * We should be able to synthesize subscripts with different + // signatures (taking into account default parameters) and even + // different accessors (e.g., synthesizing the `get` and `set` + // accessors from a `ref` accessor) + // + // * When we support property declarations, it should be possible + // to synthesize a property requirement using a field of the + // same name. + // + // * For specific kinds of generic requirements, we should be able + // to wrap the synthesis of the inner declaration in synthesis + // of an outer generic with a matching signature. + // + // All of these cases can/should use similar logic to + // `trySynthesizeMethodRequirementWitness` where they construct an AST + // in the form of what the use site ought to look like, and then + // apply existing semantic checking logic to generate the code. + + return false; + } + bool SemanticsVisitor::findWitnessForInterfaceRequirement( ConformanceCheckingContext* context, Type* type, @@ -1525,29 +1844,72 @@ namespace Slang // but would require synthesizing proxy/forwarding // implementations in the type itself. // - // We will punt on the second issue for now (since - // transparent members aren't currently exposed as - // a general-purpose feature for users), and rely - // on subsequent checking in this function to - // rule out inherited abstract members. + // For the first issue, we will use a flag to influence + // lookup so that it doesn't include results looked up + // through interface inheritance clauses (but it *will* + // look up result through inheritance clauses corresponding + // to concrete types). // - auto lookupResult = lookUpMember(m_astBuilder, this, name, type); + // The second issue of members that require us to proxy/forward + // requests will be handled further down. For now we include + // lookup results that might be usable, but not as-is. + // + auto lookupResult = lookUpMember(m_astBuilder, this, name, type, LookupMask::Default, LookupOptions::IgnoreBaseInterfaces); + + if(!lookupResult.isValid()) + { + // If we failed to even look up a member with the name of the + // requirement, then we can be certain that the type doesn't + // satisfy the requirement. + // + // TODO: If we ever allowed certain kinds of requirements to + // be inferred (e.g., inferring associated types from the + // signatures of methods, as is done for Swift), we'd + // need to revisit this step. + // + getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, type, requiredMemberDeclRef); + return false; + } // Iterate over the members and look for one that matches // the expected signature for the requirement. for (auto member : lookupResult) { + // To a first approximation, any lookup result that required a "breadcrumb" + // will not be usable to directly satisfy an interface requirement, since + // each breadcrumb will amount to a manipulation of `this` that is required + // to make the declaration usable (e.g., casting to a base type). + // + if(member.breadcrumbs != nullptr) + continue; + if (doesMemberSatisfyRequirement(member.declRef, requiredMemberDeclRef, witnessTable)) return true; } - // No suitable member found, although there were candidates. + // If we reach this point then there were no members suitable + // for satisfying the interface requirement *diretly*. + // + // It is possible that one of the items in `lookupResult` could be + // used to synthesize an exact-match witness, by generating the + // code required to handle all the conversions that might be + // required on `this`. + // + if( trySynthesizeRequirementWitness(context, lookupResult, requiredMemberDeclRef, witnessTable) ) + { + return true; + } + + // We failed to find a member of the type that can be used + // to satisfy the requirement (even via synthesis), so we + // need to report the failure to the user. // // TODO: Eventually we might want something akin to the current // overload resolution logic, where we keep track of a list // of "candidates" for satisfaction of the requirement, - // and if nothing is found we print the candidates - + // and if nothing is found we print the candidates that made it + // furthest in checking. + // getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, type, requiredMemberDeclRef); return false; } @@ -1701,7 +2063,8 @@ namespace Slang bool SemanticsVisitor::checkConformance( Type* type, - InheritanceDecl* inheritanceDecl) + InheritanceDecl* inheritanceDecl, + ContainerDecl* parentDecl) { if( auto declRefType = as(type) ) { @@ -1736,6 +2099,8 @@ namespace Slang auto baseType = inheritanceDecl->base.type; ConformanceCheckingContext context; + context.conformingType = type; + context.parentDecl = parentDecl; RefPtr witnessTable = checkConformanceToType(&context, type, inheritanceDecl, baseType); if(!witnessTable) return false; @@ -1751,7 +2116,7 @@ namespace Slang for (auto inheritanceDecl : decl->getMembersOfType()) { - checkConformance(targetType, inheritanceDecl); + checkConformance(targetType, inheritanceDecl, decl); } } @@ -1789,7 +2154,7 @@ namespace Slang // (That's what C# does). for (auto inheritanceDecl : decl->getMembersOfType()) { - checkConformance(type, inheritanceDecl); + checkConformance(type, inheritanceDecl, decl); } } } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 93ae87f40..931e331a6 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -737,9 +737,35 @@ namespace Slang // struct ConformanceCheckingContext { + /// The type for which conformances are being checked + Type* conformingType; + + /// The outer declaration for the conformances being checked (either a type or `extension` declaration) + ContainerDecl* parentDecl; + Dictionary, RefPtr> mapInterfaceToWitnessTable; }; + /// Attempt to synthesize a method that can satisfy `requiredMemberDeclRef` using `lookupResult`. + /// + /// On success, installs the syntethesized method in `witnessTable` and returns `true`. + /// Otherwise, returns `false`. + bool trySynthesizeMethodRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef requiredMemberDeclRef, + RefPtr witnessTable); + + /// Attempt to synthesize a declartion that can satisfy `requiredMemberDeclRef` using `lookupResult`. + /// + /// On success, installs the syntethesized declaration in `witnessTable` and returns `true`. + /// Otherwise, returns `false`. + bool trySynthesizeRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef requiredMemberDeclRef, + RefPtr witnessTable); + // Find the appropriate member of a declared type to // satisfy a requirement of an interface the type // claims to conform to. @@ -782,7 +808,8 @@ namespace Slang /// inheritance to be valid. bool checkConformance( Type* type, - InheritanceDecl* inheritanceDecl); + InheritanceDecl* inheritanceDecl, + ContainerDecl* parentDecl); void checkExtensionConformance(ExtensionDecl* decl); diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index 19cd4cafe..855497d00 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -527,6 +527,17 @@ static void _lookUpMembersInSuperTypeDeclImpl( for (auto inheritanceDeclRef : getMembersOfType(aggTypeDeclBaseRef)) { ensureDecl(semantics, inheritanceDeclRef.getDecl(), DeclCheckState::CanUseBaseOfInheritanceDecl); + + auto baseType = getSup(astBuilder, inheritanceDeclRef); + if( auto baseDeclRefType = as(baseType) ) + { + if( auto baseInterfaceDeclRef = baseDeclRefType->declRef.as() ) + { + if( int(request.options) & int(LookupOptions::IgnoreBaseInterfaces) ) + continue; + } + } + _lookUpMembersInSuperType(astBuilder, name, leafType, leafIsSuperWitness, inheritanceDeclRef, request, ioResult, inBreadcrumbs); } } @@ -807,11 +818,13 @@ LookupResult lookUpMember( SemanticsVisitor* semantics, Name* name, Type* type, - LookupMask mask) + LookupMask mask, + LookupOptions options) { LookupRequest request; request.semantics = semantics; request.mask = mask; + request.options = options; LookupResult result; _lookUpMembersInType(astBuilder, name, type, request, result, nullptr); diff --git a/source/slang/slang-lookup.h b/source/slang/slang-lookup.h index 1402918ad..f983bfefc 100644 --- a/source/slang/slang-lookup.h +++ b/source/slang/slang-lookup.h @@ -30,7 +30,8 @@ LookupResult lookUpMember( SemanticsVisitor* semantics, Name* name, Type* type, - LookupMask mask = LookupMask::Default); + LookupMask mask = LookupMask::Default, + LookupOptions options = LookupOptions::None); /// Perform "direct" lookup in a container declaration LookupResult lookUpDirectAndTransparentMembers( diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang new file mode 100644 index 000000000..e18695737 --- /dev/null +++ b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang @@ -0,0 +1,69 @@ +// struct-inherit-interface-requirement.slang + +//TEST(compute):COMPARE_COMPUTE: + +// Test that a `struct` type can use an inherited +// member to satisfy an interface requirement. + +interface ITweak +{ + int tweak(int val); + int twiddle(int val); +} + +// Note: `Base` intentionally doesn't inherit from `ITweak`, +// but it *does* provide a method that could satisfy one +// of the interface requirements. +// +struct Base +{ + int a; + + int tweak(int val) { return val ^ a; } +} + +struct Derived : Base, ITweak +{ + // Note: it is important for this type to have an additional + // field beyond the one in `Base`, because it ensures that + // the two types `Base` and `Derived` aren't structurally + // equivalent when compiled through HLSL (which silently allows + // certain type mismatches so long as there is a memberwise + // structural match). + int b; + + int twiddle(int val) + { + return val + b; + } +} + +int tweakAndTwiddle(T tweaker, int val) +{ + int tmp = val; + tmp = tweaker.tweak(val); + tmp = tweaker.twiddle(val); + return val; +} + + +int test(int val) +{ + Derived d; + d.a = 0xFF; + d.b = 1; + + return tweakAndTwiddle(d, val); +} + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer outputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inVal = tid; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +} diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/language-feature/inheritance/struct-inheritance.slang b/tests/language-feature/inheritance/struct-inheritance.slang new file mode 100644 index 000000000..e2cfaf25b --- /dev/null +++ b/tests/language-feature/inheritance/struct-inheritance.slang @@ -0,0 +1,58 @@ +// struct-inheritance.slang + +//TEST(compute):COMPARE_COMPUTE: + +// Test that we can define a `struct` type +// that inherits from another `struct`. + +struct Base +{ + int a; + + int tweakBase(int val) { return val ^ a; } +} + +struct Derived : Base +{ + int b; + + int tweakDerived(int val) { return tweakBase(val) + b; } +} + +int tweak(Base b, int v) +{ + return b.tweakBase(v); +} + +//TEST_INPUT:cbuffer(data=[1 2]):name=C +cbuffer C +{ + int x; + int y; +} + +int test(int val) +{ + Derived d;// = { x, y }; + d.a = x; + d.b = y; + + int result = 0; + result = result*16 + d.a; + result = result*16 + d.tweakBase(val); + result = result*16 + tweak(d, val); + result = result*16 + d.tweakDerived(val); + return result; +} + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer outputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inVal = tid; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +} diff --git a/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt b/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt new file mode 100644 index 000000000..a17826230 --- /dev/null +++ b/tests/language-feature/inheritance/struct-inheritance.slang.expected.txt @@ -0,0 +1,4 @@ +1113 +1002 +1335 +1224 -- cgit v1.2.3 From fc4342b834fbf3f20bcb90574a63859bf34462cd Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Fri, 19 Jun 2020 09:25:44 -0700 Subject: fixup: actually make the test case test something --- .../inheritance/struct-inherit-interface-requirement.slang | 6 +++--- .../struct-inherit-interface-requirement.slang.expected.txt | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang index e18695737..e1ab2333a 100644 --- a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang +++ b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang @@ -41,9 +41,9 @@ struct Derived : Base, ITweak int tweakAndTwiddle(T tweaker, int val) { int tmp = val; - tmp = tweaker.tweak(val); - tmp = tweaker.twiddle(val); - return val; + tmp = tweaker.tweak(tmp); + tmp = tweaker.twiddle(tmp); + return tmp; } diff --git a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt index e69de29bb..53246eedf 100644 --- a/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt +++ b/tests/language-feature/inheritance/struct-inherit-interface-requirement.slang.expected.txt @@ -0,0 +1,4 @@ +100 +FF +FE +FD -- cgit v1.2.3 From 19880116635a4a76af55d876b82ec966c90deafb Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Fri, 19 Jun 2020 11:50:15 -0700 Subject: fixup: review feedback --- source/slang/slang-check-decl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index ed273e2a4..600d456b8 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -1456,7 +1456,7 @@ namespace Slang // interface ICounter { [mutating] int increment(); } // struct MyCounter : ICounter // { - // int increment(int val = 1) { ... } + // [murtating] int increment(int val = 1) { ... } // } // // It is clear in this case that the `MyCounter` type *can* @@ -1469,7 +1469,7 @@ namespace Slang // struct MyCounter ... // { // ... - // int synthesized() + // [murtating] int synthesized() // { // return this.increment(); // } -- cgit v1.2.3