diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-10-18 09:30:38 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-10-18 09:30:38 -0700 |
| commit | 3a5214b65b2a5efdbcf9bf6fb4d7603e9ee63234 (patch) | |
| tree | aa77f0e7b5ca7e6327c252a8acff50ccb08a7794 | |
| parent | f9710d50bc675ddba51cc6d94b125ba1549708a8 (diff) | |
Add support for static methods in interfaces (#680)
This change allows an interface to include `static` methods as requirements, so that types that conform to the interface will need to satisfy the requirement with a `static` method.
The essence of the check is simple: when checking that a method satisfies a requirement, we enforce that both are `static` or both are non-`static`.
Making that simple change and adding a test change broke a few other places in the compiler that this change tries to fix. The main fix is to handle cases where we might look up an "effectively static" member of a type through an instance, and to make sure that we replace the instance-based lookup with type-based lookup. There was already logic along these lines in `lower-to-ir.cpp`, so this change centralizes it in `check.cpp` where it seems to logically belong.
| -rw-r--r-- | source/slang/check.cpp | 92 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 41 | ||||
| -rw-r--r-- | tests/compute/interface-static-method.slang | 56 | ||||
| -rw-r--r-- | tests/compute/interface-static-method.slang.expected.txt | 4 |
4 files changed, 159 insertions, 34 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 985c5c0b7..47713e6cc 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -9,6 +9,75 @@ namespace Slang { + + /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration? + bool isEffectivelyStatic( + Decl* decl, + ContainerDecl* parentDecl) + { + // Things at the global scope are always "members" of their module. + // + if(parentDecl->As<ModuleDecl>()) + return false; + + // Anything explicitly marked `static` and not at module scope + // counts as a static rather than instance declaration. + // + if(decl->HasModifier<HLSLStaticModifier>()) + return true; + + // Next we need to deal with cases where a declaration is + // effectively `static` even if the language doesn't make + // the user say so. Most languages make the default assumption + // that nested types are `static` even if they don't say + // so (Java is an exception here, perhaps due to some + // influence from the Scandanavian OOP tradition of Beta/gbeta). + // + if(dynamic_cast<AggTypeDecl*>(decl)) + return true; + if(dynamic_cast<SimpleTypeDecl*>(decl)) + return true; + + // Things nested inside functions may have dependencies + // on values from the enclosing scope, but this needs to + // be dealt with via "capture" so they are also effectively + // `static` + // + if(dynamic_cast<FunctionDeclBase*>(parentDecl)) + return true; + + // Type constraint declarations are used in member-reference + // context as a form of casting operation, so we treat them + // as if they are instance members. This is a bit of a hack, + // but it achieves the result we want until we have an + // explicit representation of up-cast operations in the + // AST. + // + if(decl->As<TypeConstraintDecl>()) + return false; + + return false; + } + + /// Should the given `decl` be treated as a static rather than instance declaration? + bool isEffectivelyStatic( + Decl* decl) + { + // For the purposes of an ordinary declaration, when determining if + // it is static or per-instance, the "parent" declaration we really + // care about is the next outer non-generic declaration. + // + // TODO: This idiom of getting the "next outer non-generic declaration" + // comes up just enough that we should probably have a convenience + // function for it. + + auto parentDecl = decl->ParentDecl; + if(auto genericDecl = parentDecl->As<GenericDecl>()) + parentDecl = genericDecl->ParentDecl; + + return isEffectivelyStatic(decl, parentDecl); + } + // A flat representation of basic types (scalars, vectors and matrices) // that can be used as lookup key in caches struct BasicTypeKey @@ -416,6 +485,22 @@ namespace Slang expr->declRef = declRef; return expr; } + else if(isEffectivelyStatic(declRef.getDecl())) + { + // Extract the type of the baseExpr + auto baseExprType = baseExpr->type.type; + RefPtr<SharedTypeExpr> baseTypeExpr = new SharedTypeExpr(); + baseTypeExpr->base.type = baseExprType; + baseTypeExpr->type = new TypeType(baseExprType); + + auto expr = new StaticMemberExpr(); + expr->loc = loc; + expr->type = type; + expr->BaseExpression = baseTypeExpr; + expr->name = declRef.GetName(); + expr->declRef = declRef; + return expr; + } else { // If the base expression wasn't a type, then this @@ -2246,6 +2331,13 @@ namespace Slang return false; } + if(satisfyingMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>() + != requiredMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()) + { + // A `static` method can't satisfy a non-`static` requirement and vice versa. + return false; + } + // TODO: actually implement matching here. For now we'll // just pretend that things are satisfied in order to make progress.. witnessTable->requirementDictionary.Add( diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 3c4067af5..7b63f510f 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -384,6 +384,12 @@ void setValue(IRGenContext* context, Decl* decl, LoweredValInfo value) context->env->mapDeclToValue[decl] = value; } + + /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration? +bool isEffectivelyStatic( + Decl* decl, + ContainerDecl* parentDecl); + // Ensure that a version of the given declaration has been emitted to the IR LoweredValInfo ensureDecl( IRGenContext* context, @@ -4446,46 +4452,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // // We also need to be able to detect whether a declaration is // either explicitly or implicitly treated as `static`: - bool isMemberDeclarationEffectivelyStatic( - Decl* decl, - ContainerDecl* parentDecl) - { - // Anything explicitly marked `static` counts. - // - // There is a subtle detail here with a global-scope `static` - // variable not really meaning `static` in the same way, but - // it doesn't matter because the module shouldn't introduce - // any parameters we care about. - if(decl->HasModifier<HLSLStaticModifier>()) - return true; - - // Next we need to deal with cases where a declaration is - // effectively `static` even if the language doesn't make - // the user say so. Most languages make the default assumption - // that nested types are `static` even if they don't say - // so (Java is an exception here, perhaps due to some - // includence from the Scandanavian OOP tradition). - if(dynamic_cast<AggTypeDecl*>(decl)) - return true; - - // Things nested inside functions may have dependencies - // on values from the enclosing scope, but this needs to - // be dealt with via "capture" so they are also effectively - // `static` - if(dynamic_cast<FunctionDeclBase*>(parentDecl)) - return true; - - return false; - } - // We also need to be able to detect whether a declaration is - // either explicitly or implicitly treated as `static`: ParameterListCollectMode getModeForCollectingParentParameters( Decl* decl, ContainerDecl* parentDecl) { // If we have a `static` parameter, then it is obvious // that we should use the `static` mode - if(isMemberDeclarationEffectivelyStatic(decl, parentDecl)) + if(isEffectivelyStatic(decl, parentDecl)) return kParameterListCollectMode_Static; // Otherwise, let's default to collecting everything diff --git a/tests/compute/interface-static-method.slang b/tests/compute/interface-static-method.slang new file mode 100644 index 000000000..4747c8a8a --- /dev/null +++ b/tests/compute/interface-static-method.slang @@ -0,0 +1,56 @@ +// interface-static-method.slang + +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute + +interface IHideout +{ + int getAnimalCount(); +} + +interface ISuperhero +{ + associatedtype Hideout : IHideout; + + static Hideout getHideout(); +} + +struct Batcave : IHideout +{ + int batCount; + + int getAnimalCount() { return batCount; } +} + +struct Batman : ISuperhero +{ + typedef Batcave Hideout; + + static Batcave getHideout() + { + Batcave batcave = { 100 }; + return batcave; + } +} + +int doIt<T:ISuperhero>() +{ + return T.getHideout().getAnimalCount(); +} + +int test(int val) +{ + return doIt<Batman>(); +} + + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out +RWStructuredBuffer<int> outputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + int tid = dispatchThreadID.x; + outputBuffer[tid] = test(tid); +}
\ No newline at end of file diff --git a/tests/compute/interface-static-method.slang.expected.txt b/tests/compute/interface-static-method.slang.expected.txt new file mode 100644 index 000000000..b1b8e46f4 --- /dev/null +++ b/tests/compute/interface-static-method.slang.expected.txt @@ -0,0 +1,4 @@ +64 +64 +64 +64 |
