From 2bfe62afb381f23dcbd39dfd6ba7448050272861 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Thu, 13 Aug 2020 17:56:20 -0700 Subject: Support property declarations in interfaces (#1494) There are two main features in this change. First, we allow for `interface`s to declare `property` requirements, which can be satisfied by matching `property` declarations in a type that conforms to the interface: interface IRectangle { property float width { get; } property float height { get; } } struct Square : IRectangle { float size; property float width { get { return size; } } property float height { get { return size; } } } Second, we allow a type to satisfy a `property` requirement with an ordinary field of the same name: struct Rectangle : IRectangle { float width; float height; // no explicit `property` declarations needed } The implementation of these features is mostly in `slang-check-decl.cpp` in the logic for checking conformance of a type to an interface. The first feature simply requires adding logic to checking whether a candidate satisfying `property` declaration matches a required `property` declaration. To do so, it must have the same type, and an accessor to satisfy each of the required accessors. The second feature requires adding logic to synthesize an AST `property` declaration for a type, based on a required `property` declaration and its accessors. This means that, more or less, any type where `this.name` yields a storage location that does what is needed can satisfy a property requirement (there is no specific rule that says the storage needs to be a field, although that is the most likely case). The way that witnesses are stored for property declarations probably merits some description. During IR lowering, an abstract storage declaration like a subscript or `property` more or less desugars away, so that the actual interface requirements correspond to the accessors within it (the `get`, `set`, etc.). This means that a witness table should have entries/keys corresponding to the accessors and not the property itself. The process of finding/recording witnesses for `property` requirements thus installs entries for the individual accessors (with care taken to only install accessor witnesses once we are sure we have witnesses for all the requirements). Currently, the code also installs an entry for the property itself, although that is not strictly required, and might not be something we continue to do long-term. (Aside: it was somewhat surprising that an end-to-end test of `property` declarations in `interface`s Just Worked without any changes to IR lowering.) As we continue to write more code that synthesizes and checks AST expressions/statements, it becomes necessary to refactor the semantic checking logic so that it splits the recursive part (e.g., checking the operands of an assignment) from the validation part (e.g., checking that the assignment itself is valid). It is probably too big of a change to justify at this point, but it might be valuable in the future to have distinct hierarchies that represent unchecked and checked ASTs, with semantic checking mostly being a transformation from one to the other. The benefit of such a change is we could factor out a distinct "builder" API for constructing validated/checked AST nodes, with both semantic checking and AST synthesis being clients of that API. --- .../properties/property-in-interface.slang | 47 ++++++++++++++++++++++ .../property-in-interface.slang.expected.txt | 4 ++ 2 files changed, 51 insertions(+) create mode 100644 tests/language-feature/properties/property-in-interface.slang create mode 100644 tests/language-feature/properties/property-in-interface.slang.expected.txt (limited to 'tests') diff --git a/tests/language-feature/properties/property-in-interface.slang b/tests/language-feature/properties/property-in-interface.slang new file mode 100644 index 000000000..ba6d5eaa9 --- /dev/null +++ b/tests/language-feature/properties/property-in-interface.slang @@ -0,0 +1,47 @@ +// property-in-interface.slang + +//TEST(compute):COMPARE_COMPUTE: + +// Test that interfaces can include property declarations. + +interface ICell +{ + property value : int { get; set; } +} + +struct MyCell : ICell +{ + var _data : int; + + property value : int { get { return _data; } set(newValue) { _data = newValue; } } +} + +struct YourCell : ICell +{ + int value; +} + +int helper(C cell) +{ + cell.value = cell.value + 1; + return cell.value; +} + +int test(int value) +{ + MyCell myCell = { value+1 }; + YourCell yourCell = { value }; + return helper(myCell)*16 + helper(yourCell); +} + +//TEST_INPUT:ubuffer(data=[0 1 2 3], stride=4):out,name=outputBuffer +RWStructuredBuffer outputBuffer : register(u0); + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inVal = outputBuffer[tid]; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +} diff --git a/tests/language-feature/properties/property-in-interface.slang.expected.txt b/tests/language-feature/properties/property-in-interface.slang.expected.txt new file mode 100644 index 000000000..ba2ec282d --- /dev/null +++ b/tests/language-feature/properties/property-in-interface.slang.expected.txt @@ -0,0 +1,4 @@ +21 +32 +43 +54 -- cgit v1.2.3