diff options
| -rw-r--r-- | source/slang/slang-ast-decl.h | 12 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 54 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 1 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 50 | ||||
| -rw-r--r-- | tests/language-feature/namespaces/using-namespace.slang | 47 | ||||
| -rw-r--r-- | tests/language-feature/namespaces/using-namespace.slang.expected.txt | 4 |
7 files changed, 163 insertions, 6 deletions
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index 833acd681..e88260414 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -373,6 +373,18 @@ class ModuleDecl : public NamespaceDeclBase Dictionary<AggTypeDecl*, RefPtr<CandidateExtensionList>> mapTypeToCandidateExtensions; }; + /// A declaration that brings members of another declaration or namespace into scope +class UsingDecl : public Decl +{ + SLANG_CLASS(UsingDecl) + + /// An expression that identifies the entity (e.g., a namespace) to be brought into `scope` + Expr* arg; + + /// The scope that the entity named by `arg` will be brought into + RefPtr<Scope> scope; +}; + class ImportDecl : public Decl { SLANG_CLASS(ImportDecl) diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 10e3818c4..4ea8aa7f6 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -56,6 +56,8 @@ namespace Slang void visitImportDecl(ImportDecl* decl); + void visitUsingDecl(UsingDecl* decl); + void visitGenericTypeParamDecl(GenericTypeParamDecl* decl); void visitGenericValueParamDecl(GenericValueParamDecl* decl); @@ -4615,6 +4617,58 @@ namespace Slang } } + void SemanticsDeclHeaderVisitor::visitUsingDecl(UsingDecl* decl) + { + // First, we need to look up whatever the argument of the `using` + // declaration names. + // + decl->arg = CheckTerm(decl->arg); + + // Next, we want to ensure that whatever is being named by `decl->arg` + // is a namespace (or a module, since modules are namespace-like). + // + // TODO: The logic here assumes that we can't have multiple `NamespaceDecl`s + // with the same name in scope, but that assumption is only valid in the + // context of a single module (where we deduplicate `namespace`s during + // parsing). If a user `import`s multiple modules that all have namespaces + // of the same name, it would be possible for `decl->arg` to be overloaded. + // In that case we should really iterate over all the entities that are + // named and import any that are namespace-like. + // + NamespaceDeclBase* namespaceDecl = nullptr; + if( auto declRefExpr = as<DeclRefExpr>(decl->arg) ) + { + if( auto namespaceDeclRef = declRefExpr->declRef.as<NamespaceDeclBase>() ) + { + SLANG_ASSERT(!namespaceDeclRef.substitutions.substitutions); + namespaceDecl = namespaceDeclRef.getDecl(); + } + } + if( !namespaceDecl ) + { + getSink()->diagnose(decl->arg, Diagnostics::expectedANamespace, decl->arg->type); + return; + } + + // Once we have identified the namespace to bring into scope, + // we need to create a new sibling sub-scope to add to the + // lookup scope that was in place when the `using` was parsed. + // + // Subsequent lookup in that scope will walk through our new + // sub-scope and see the namespace. + // + // TODO: If we update the `containerDecl` in a scope to allow + // for a more general `DeclRef`, or even a full `DeclRefExpr`, + // then it would be possible for `using` to apply to more kinds + // of entities than just namespaces. + // + auto scope = decl->scope; + auto subScope = new Scope(); + subScope->containerDecl = namespaceDecl; + subScope->nextSibling = scope->nextSibling; + scope->nextSibling = subScope; + } + /// Get a reference to the candidate extension list for `typeDecl` in the given dictionary /// /// Note: this function creates an empty list of candidates for the given type if diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 3e0d488f9..c268fd241 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -255,6 +255,7 @@ DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on ty DIAGNOSTIC(30043, Error, getStringHashRequiresStringLiteral, "getStringHash parameter can only accept a string literal") DIAGNOSTIC(30060, Error, expectedAType, "expected a type, got a '$0'") +DIAGNOSTIC(30061, Error, expectedANamespace, "expected a namespace, got a '$0'") DIAGNOSTIC(30100, Error, staticRefToNonStaticMember, "type '$0' cannot be used to refer to non-static member '$1'") diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 864491f7e..f87b6bd69 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -5032,6 +5032,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> LoweredValInfo visit##NAME(NAME*) { return LoweredValInfo(); } IGNORED_CASE(ImportDecl) + IGNORED_CASE(UsingDecl) IGNORED_CASE(EmptyDecl) IGNORED_CASE(SyntaxDecl) IGNORED_CASE(AttributeDecl) diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index f2bc2eb9a..e02ccf245 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -933,12 +933,16 @@ namespace Slang return parser->getNamePool()->getName(text); } + static bool expect(Parser* parser, TokenType tokenType) + { + return parser->ReadToken(tokenType).type == tokenType; + } + static NameLoc expectIdentifier(Parser* parser) { return NameLoc(parser->ReadToken(TokenType::Identifier)); } - static NodeBase* parseImportDecl( Parser* parser, void* /*userData*/) { @@ -2629,6 +2633,44 @@ namespace Slang return result; } + static NodeBase* parseUsingDecl(Parser* parser, void* /*userData*/) + { + UsingDecl* decl = parser->astBuilder->create<UsingDecl>(); + parser->FillPosition(decl); + + // A `using` declaration will need to know about the current + // scope at the point where it appears, so that it can know + // the scope it is attempting to extend. + // + decl->scope = parser->currentScope; + + // TODO: We may eventually want to support declarations + // of the form `using <id> = <expr>;` which introduce + // a shorthand alias for a namespace/type/whatever. + // + // For now we are just sticking to the most basic form. + + // As a compatibility feature for programmers used to C++, + // we allow the `namespace` keyword to come after `using`, + // where it has no effect. + // + if(parser->LookAheadToken("namespace")) + { + advanceToken(parser); + } + + // The entity that is going to be used is identified + // using an arbitrary expression (although we expect + // that valid code will not typically use the full + // freedom of what the expression grammar supports. + // + decl->arg = parser->ParseExpression(); + + expect(parser, TokenType::Semicolon); + + return decl; + } + static NodeBase* parseConstructorDecl(Parser* parser, void* /*userData*/) { ConstructorDecl* decl = parser->astBuilder->create<ConstructorDecl>(); @@ -2750,11 +2792,6 @@ namespace Slang return decl; } - static bool expect(Parser* parser, TokenType tokenType) - { - return parser->ReadToken(tokenType).type == tokenType; - } - /// Peek in the token stream and return `true` if it looks like a modern-style variable declaration is coming up. static bool _peekModernStyleVarDecl(Parser* parser) { @@ -5659,6 +5696,7 @@ namespace Slang DECL(typealias, parseTypeAliasDecl); DECL(__generic_value_param, parseGlobalGenericValueParamDecl); DECL(namespace, parseNamespaceDecl); + DECL(using, parseUsingDecl); #undef DECL diff --git a/tests/language-feature/namespaces/using-namespace.slang b/tests/language-feature/namespaces/using-namespace.slang new file mode 100644 index 000000000..b0b301929 --- /dev/null +++ b/tests/language-feature/namespaces/using-namespace.slang @@ -0,0 +1,47 @@ +// using-namespace.slang + +// Test that `using` can bring declarations from a namespace into scope + +//TEST(compute):COMPARE_COMPUTE: + +namespace X +{ + struct A { int a; } +} + +namespace Y +{ + // A `using` that brings one namespace into scope of declarations in another + // + using X; + + int b(A a) { return a.a; } +} + +// A `using` that brings declarations in a namespace into the global scope +// +using Y; + +int test(int value) +{ + // A `using` that brings declarations into a local scope + // (and also uses the optional `namespace` placeholder keyword) + // + using namespace X; + + A a = { value }; + return b(a); +} + +//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) +{ + uint tid = dispatchThreadID.x; + int inVal = tid; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +} + diff --git a/tests/language-feature/namespaces/using-namespace.slang.expected.txt b/tests/language-feature/namespaces/using-namespace.slang.expected.txt new file mode 100644 index 000000000..bc856dafa --- /dev/null +++ b/tests/language-feature/namespaces/using-namespace.slang.expected.txt @@ -0,0 +1,4 @@ +0 +1 +2 +3 |
