diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2020-08-11 11:37:38 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-11 11:37:38 -0400 |
| commit | 96805c71696b3cce2bdd020a16574d88a80be1eb (patch) | |
| tree | 20824eb8f560140c913cddf779ed465ea2ba4955 | |
| parent | 2903eb53ed9411d1160d2b777992ba9f36d0c746 (diff) | |
Improvements to Casting (#1483)
* Improve handling of cast detection when have a more complex cast than just a single identifier.
* Improve comments around heuristic for casting
* Added nested enum test.
* Improve comments
* Define function like - output change.
* Use lookup for types in determining if cast or not.
* Add _isCast function
* Add heuristic test to nested-enum.slang that works if the type test fails.
* Change hueristic based on review.
Allow (..)( to always be an expression, because if it's a type it will be turned into a cast later.
* Fix output of define-function-like.slang - which changes again with improved casting support.
* Improve testing for type in cast - if we find a decl and it's not a type, then we know it's not a cast.
| -rw-r--r-- | source/slang/slang-parser.cpp | 236 | ||||
| -rw-r--r-- | tests/language-feature/enums/nested-enum.slang | 84 | ||||
| -rw-r--r-- | tests/language-feature/enums/nested-enum.slang.expected.txt | 4 |
3 files changed, 306 insertions, 18 deletions
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index ed20e0d2e..460f781fe 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -3380,6 +3380,61 @@ namespace Slang return stmt; } + static bool _isType(Decl* decl) + { + return decl && (as<AggTypeDecl>(decl) || as<SimpleTypeDecl>(decl)); + } + + // TODO(JS): + // This only handles StaticMemberExpr, and VarExpr lookup scenarios! + static Decl* _tryResolveDecl(Parser* parser, Expr* expr) + { + if (auto staticMemberExpr = as<StaticMemberExpr>(expr)) + { + Decl* baseTypeDecl = _tryResolveDecl(parser, staticMemberExpr->baseExpression); + if (!baseTypeDecl) + { + return nullptr; + } + if (AggTypeDecl* aggTypeDecl = as<AggTypeDecl>(baseTypeDecl)) + { + // TODO(JS): + // Is it valid to always have empty substitution set here? + DeclRef<ContainerDecl> declRef(aggTypeDecl, SubstitutionSet()); + + auto lookupResult = lookUpDirectAndTransparentMembers( + parser->astBuilder, + nullptr, // no semantics visitor available yet + staticMemberExpr->name, + declRef); + + if (!lookupResult.isValid() || lookupResult.isOverloaded()) + return nullptr; + + return lookupResult.item.declRef.getDecl(); + } + + // Didn't find it + return nullptr; + } + + if (auto varExpr = as<VarExpr>(expr)) + { + // Do the lookup in the current scope + auto lookupResult = lookUp( + parser->astBuilder, + nullptr, // no semantics visitor available yet + varExpr->name, + parser->currentScope); + if (!lookupResult.isValid() || lookupResult.isOverloaded()) + return nullptr; + + return lookupResult.item.declRef.getDecl(); + } + + return nullptr; + } + static bool isTypeName(Parser* parser, Name* name) { auto lookupResult = lookUp( @@ -3390,19 +3445,7 @@ namespace Slang if(!lookupResult.isValid() || lookupResult.isOverloaded()) return false; - auto decl = lookupResult.item.declRef.getDecl(); - if( auto typeDecl = as<AggTypeDecl>(decl) ) - { - return true; - } - else if( auto typeVarDecl = as<SimpleTypeDecl>(decl) ) - { - return true; - } - else - { - return false; - } + return _isType(lookupResult.item.declRef.getDecl()); } static bool peekTypeName(Parser* parser) @@ -4310,6 +4353,137 @@ namespace Slang return value; } + static bool _isCast(Parser* parser, Expr* expr) + { + + // We can't just look at expr and look up if it's a type, because we allow + // out-of-order declarations. So to a first approximation we'll try and + // determine if it is a cast via a heuristic based on what comes next + + TokenType tokenType = peekTokenType(parser); + + // Expression + // ========== + // + // Misc: ; ) [ ] , . = ? (ternary) { } ++ -- -> + // Binary ops: * / | & ^ % << >> + // Logical ops: || && + // Comparisons: != == < > <= => + // + // Any assign op + // + // If we don't have pointers then + // & : (Thing::Another) &v + // * : (Thing::Another)*ptr is a cast. + // + // Cast + // ==== + // + // Misc: ( + // Identifier, Literal + // Unary ops: !, ~ + // + // Ambiguous + // ========= + // + // - : Can be unary and therefore a cast or a binary subtract, and therefore an expression + // + : Can be unary and therefore could be a cast, or a binary add and therefore an expression + // + // Arbitrary + // ========= + // + // End of file, End of directive, Invalid, :, :: + + switch (tokenType) + { + case TokenType::FloatingPointLiteral: + case TokenType::CharLiteral: + case TokenType::IntegerLiteral: + case TokenType::Identifier: + case TokenType::OpNot: + case TokenType::OpBitNot: + { + // If followed by one of these, must be a cast + return true; + } + case TokenType::LParent: + { + // If we are followed by ( it might not be a cast - it could be a call invocation. + // BUT we can always *assume* it is a call, because such a 'call' will be correctly + // handled as a cast if necessary later. + return false; + } + case TokenType::OpAdd: + case TokenType::OpSub: + { + // + - are ambiguous, it could be a binary + or - so -> expression, or unary -> cast + // + // (Some::Stuff) + 3 + // (Some::Stuff) - 3 + // Strictly I can only tell if this is an expression or a cast if I know Some::Stuff is a type or not + // but we can't know here in general because we allow out-of-order declarations. + + // If we can determine it's a type, then it must be a cast, and we are done. + // + // NOTE! This test can only determine if it's a type *iff* it has already been defined. A future out + // of order declaration, will not be correctly found here. + // + // This means the semantics change depending on the order of definition (!) + Decl* decl = _tryResolveDecl(parser, expr); + // If we can find the decl-> we can resolve unambiguously + if (decl) + { + return _isType(decl); + } + + // Now we use a heuristic. + // + // Whitespace before, whitespace after->binary + // No whitespace before, no whitespace after->binary + // Otherwise->unary + // + // Unary -> cast, binary -> expression. + // + // Ie: + // (Some::Stuff) +3 - must be a cast + // (Some::Stuff)+ 3 - must be a cast (?) This is a bit odd. + // (Some::Stuff) + 3 - must be an expression. + // (Some::Stuff)+3 - must be an expression. + + // TODO(JS): This covers the (SomeScope::Identifier) case + // + // But perhaps there other ways of referring to types, that this now misses? With associated types/generics perhaps. + // + // For now we'll assume it's not a cast if it's not a StaticMemberExpr + // The reason for the restriction (which perhaps can be broadened), is we don't + // want the interpretation of something in parentheses to be determined by something as common as + or - whitespace. + + if (auto staticMemberExpr = dynamicCast<StaticMemberExpr>(expr)) + { + // Apply the heuristic: + TokenReader::ParsingCursor cursor = parser->tokenReader.getCursor(); + // Skip the + or - + const Token opToken = advanceToken(parser); + // Peek the next token to see if it was preceded by white space + const Token nextToken = peekToken(parser); + + // Rewind + parser->tokenReader.setCursor(cursor); + + const bool isBinary = (nextToken.flags & TokenFlag::AfterWhitespace) == (opToken.flags & TokenFlag::AfterWhitespace); + + // If it's binary it's not a cast + return !isBinary; + } + break; + } + default: break; + } + + // We'll assume it's not a cast + return false; + } + static Expr* parseAtomicExpr(Parser* parser) { switch( peekTokenType(parser) ) @@ -4320,7 +4494,7 @@ namespace Slang return nullptr; // Either: - // - parenthized expression `(exp)` + // - parenthesized expression `(exp)` // - cast `(type) exp` // // Proper disambiguation requires mixing up parsing @@ -4344,13 +4518,39 @@ namespace Slang } else { + // The above branch catches the case where we have a cast like (Thing), but with + // the scoping operator it will not handle (SomeScope::Thing). In that case this + // branch will be taken. This is okay in so far as SomeScope::Thing will parse + // as an expression. + Expr* base = parser->ParseExpression(); + parser->ReadToken(TokenType::RParent); - ParenExpr* parenExpr = parser->astBuilder->create<ParenExpr>(); - parenExpr->loc = openParen.loc; - parenExpr->base = base; - return parenExpr; + // We now try and determine by what base is, if this is actually a cast or an expression in parentheses + if (_isCast(parser, base)) + { + // Parse as a cast + + TypeCastExpr* tcexpr = parser->astBuilder->create<ExplicitCastExpr>(); + tcexpr->loc = openParen.loc; + + tcexpr->functionExpr = base; + + auto arg = parsePrefixExpr(parser); + tcexpr->arguments.add(arg); + + return tcexpr; + } + else + { + // Pass as an expression in parentheses + + ParenExpr* parenExpr = parser->astBuilder->create<ParenExpr>(); + parenExpr->loc = openParen.loc; + parenExpr->base = base; + return parenExpr; + } } } diff --git a/tests/language-feature/enums/nested-enum.slang b/tests/language-feature/enums/nested-enum.slang new file mode 100644 index 000000000..942a9e72d --- /dev/null +++ b/tests/language-feature/enums/nested-enum.slang @@ -0,0 +1,84 @@ +// nested-num.slang + +// Test enums defined nested in a struct work as expected. + +//TEST(compute):COMPARE_COMPUTE: + +struct Outer +{ + enum Channel + { + Red, + Green, + Blue, + Alpha, + } + + static int doSomething(int v) { return v + 1; } + typedef int SomeType; + + static int someValue = 10; + + static int getHeuristicResult() + { + // This tests that the cast heuristic works here. + // when the compiler cannot determine if it's a type or not at this point, + // because these declarations have not been seen. + + // Has whitespace (between + and the next thing) -> must be an expression + int value = (Inner::anotherValue) + 1; + // No whitespace, so assumed to be a cast + Inner::Enum anotherValue = (Inner::Enum) +1; + + return Inner::doSomethingElse(value + (int)anotherValue); + } + + struct Inner + { + enum Enum + { + A, + B, + }; + + static int anotherValue = 10; + static int doSomethingElse(int a) { return a - 1; } + } +} + +int test(int val) +{ + Outer::Channel channel = (Outer::Channel)val; // Outer::Channel(val); + Outer::Channel otherChannel = channel; // (Outer::Channel)val; + + typedef Outer::Channel Channel; + + int result = 0; + if(channel == Channel.Red) result += 1; + if(channel != Channel.Green) result += 16; + if(otherChannel == Channel.Blue) result += 16*16; + if(otherChannel != Channel.Alpha) result += 16*16*16; + + return result; +} + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<int> outputBuffer; + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + int value = (Outer::someValue) + 1 + Outer::getHeuristicResult(); + Outer::Channel anotherValue = (Outer::Channel) +1; + + // These work because the type can be determined because they are already declared at this point + // Check can see this is a function call + value = (Outer::doSomething)(value); + // Check can see this is a cast + value += (Outer::SomeType)(value); + + uint tid = dispatchThreadID.x; + int inVal = tid; + int outVal = test(inVal) + value * 2 + int(anotherValue) * 4; + outputBuffer[tid] = outVal; +} diff --git a/tests/language-feature/enums/nested-enum.slang.expected.txt b/tests/language-feature/enums/nested-enum.slang.expected.txt new file mode 100644 index 000000000..9a26c2421 --- /dev/null +++ b/tests/language-feature/enums/nested-enum.slang.expected.txt @@ -0,0 +1,4 @@ +1071 +1060 +1170 +70 |
