diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-08-06 12:14:52 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-06 12:14:52 -0700 |
| commit | 81ce78d08a7e3fbe74f2fd41c5a258ea4b078245 (patch) | |
| tree | 06e0c3cae361ee85cbee513ee539d9f9bde647f9 /source | |
| parent | 89758544c45a15e546a1eb08891e2787bb88de4a (diff) | |
Add support for the HLSL "cast from zero" idiom (#1008)
If the user writes code like this:
MyStruct s = (MyStruct) 0;
then we will interpret it as if they had written:
MyStruct s = {};
That is, the "cast from zero" idiom will be taken as a legacy syntax for default construction (using an empty initializer list). This will be semantically equivalent to zero-initialization for all existing HLSL code (where `struct` fields can't have default initialization expressions defined), and is the easiest option for us to support in Slang (since we already support default-initialization using empty initializer lists).
The implementation of this feature is narrowly scoped:
* It only targets explicit cast expressions like `(MyStruct) 0` and not "constructor" syntax like `MyStruct(0)`
* It only applies when there is a single argument that is exactly an integer literal with a zero value (not a reference to a `static const int` that happens to be zero).
This change adds a test case to make sure that the feature works as expected. Because it relies on our existing initializer-list handling, the "cast from zero" idiom should work for any user-defined type where an initializer list would work.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-check.cpp | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/source/slang/slang-check.cpp b/source/slang/slang-check.cpp index 534642a1c..edf41fff0 100644 --- a/source/slang/slang-check.cpp +++ b/source/slang/slang-check.cpp @@ -8464,6 +8464,21 @@ namespace Slang context.baseExpr = funcOverloadExpr2->base; } + // TODO: We should have a special case here where an `InvokeExpr` + // with a single argument where the base/func expression names + // a type should always be treated as an explicit type coercion + // (and hence bottleneck through `coerce()`) instead of just + // as a constructor call. + // + // Such a special-case would help us handle cases of identity + // casts (casting an expression to the type it already has), + // without needing dummy initializer/constructor declarations. + // + // Handling that special casing here (rather than in, say, + // `visitTypeCastExpr`) would allow us to continue to ensure + // that `(T) expr` and `T(expr)` continue to be semantically + // equivalent in (almost) all cases. + if (!context.bestCandidate) { AddOverloadCandidates(funcExpr, context); @@ -8885,6 +8900,71 @@ namespace Slang arg = CheckExpr(arg); } + // LEGACY FEATURE: As a backwards-compatibility feature + // for HLSL, we will allow for a cast to a `struct` type + // from a literal zero, with the semantics of default + // initialization. + // + if( auto declRefType = typeExp.type.as<DeclRefType>() ) + { + if(auto structDeclRef = declRefType->declRef.as<StructDecl>()) + { + if( expr->Arguments.getCount() == 1 ) + { + auto arg = expr->Arguments[0]; + if( auto intLitArg = arg.as<IntegerLiteralExpr>() ) + { + if(getIntegerLiteralValue(intLitArg->token) == 0) + { + // At this point we have confirmed that the cast + // has the right form, so we want to apply our special case. + // + // TODO: If/when we allow for user-defined initializer/constructor + // definitions we would have to be careful here because it is + // possible that the target type has defined an initializer/constructor + // that takes a single `int` parmaeter and means to call that instead. + // + // For now that should be a non-issue, and in a pinch such a user + // could use `T(0)` instead of `(T) 0` to get around this special + // HLSL legacy feature. + + // We will type-check code like: + // + // MyStruct s = (MyStruct) 0; + // + // the same as: + // + // MyStruct s = {}; + // + // That is, we construct an empty initializer list, and then coerce + // that initializer list expression to the desired type (letting + // the code for handling initializer lists work out all of the + // details of what is/isn't valid). This choice means we get + // to benefit from the existing codegen support for initializer + // lists, rather than needing the `(MyStruct) 0` idiom to be + // special-cased in later stages of the compiler. + // + // Note: we use an empty initializer list `{}` instead of an + // initializer list with a single zero `{0}`, which is semantically + // significant if the first field of `MyStruct` had its own + // default initializer defined as part of the `struct` definition. + // Basically we have chosen to interpret the "cast from zero" syntax + // as sugar for default initialization, and *not* specifically + // for zero-initialization. That choice could be revisited if + // users express displeasure. For now there isn't enough usage + // of explicit default initializers for `struct` fields to + // make this a major concern (since they aren't supported in HLSL). + // + RefPtr<InitializerListExpr> initListExpr = new InitializerListExpr(); + auto checkedInitListExpr = visitInitializerListExpr(initListExpr); + return coerce(typeExp.type, initListExpr); + } + } + } + } + } + + // Now process this like any other explicit call (so casts // and constructor calls are semantically equivalent). return CheckInvokeExprWithCheckedOperands(expr); |
