summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-08-20 08:23:51 -0700
committerGitHub <noreply@github.com>2020-08-20 08:23:51 -0700
commit11748a75e66c2bd3fa7ef7635fd35363465f599c (patch)
treec1920f54ac59dcd79806b475df3e4c1ba85756ba
parentb5a4161a801a573179b1f552e5c53748d2667b03 (diff)
Initial support for a using construct (#1506)
The basic idea is that if you have a namespace: namespace MyCoolNamespace { void f() { ... } ... } then you can bring the declarations from that namespace into scope with: using MyCoolNamespace; f(); The `using` construct is allowed in any scope where declarations are allowed. As an additional feature, the construct allows and then ignores the keyword `namespace` if it occurs right after `using`: using namespace MyCoolNamespace; Note that unlike in C++, `using` a namespace inside another namespace doesn't implicitly make the symbols available to clients of that namespace: namespace hidden { void secret() {...} ... } namespace api { using hidden; ... } api.secret(); // ERROR: `secret()` isn't a member of `api` The implementation of this feature was relatively simple, although it does leave out more advanced features that might be desirable in the future: * No support for `using MCN = MyCoolNamespace` sorts of tricks to define a short name * No support for `using` anything that isn't a namespace (e.g., to make the members of a type available without qualification) * No support for cases where multiple visible modules have a namespace of the same name (or dealing with overloaded namespaces in general)
-rw-r--r--source/slang/slang-ast-decl.h12
-rw-r--r--source/slang/slang-check-decl.cpp54
-rw-r--r--source/slang/slang-diagnostic-defs.h1
-rw-r--r--source/slang/slang-lower-to-ir.cpp1
-rw-r--r--source/slang/slang-parser.cpp50
-rw-r--r--tests/language-feature/namespaces/using-namespace.slang47
-rw-r--r--tests/language-feature/namespaces/using-namespace.slang.expected.txt4
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