summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-parser.cpp
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2025-06-13 22:13:00 -0700
committerGitHub <noreply@github.com>2025-06-13 22:13:00 -0700
commit6a23949f07f4eba38086b656e7073ce3bf8cd2fe (patch)
tree132bbe330b6027d323c74175686d006605e4da6d /source/slang/slang-parser.cpp
parente72b3325663ab6d4bb791742574b031f0df6328a (diff)
Allow interface methods to have default implementations. (#7439)
Diffstat (limited to 'source/slang/slang-parser.cpp')
-rw-r--r--source/slang/slang-parser.cpp148
1 files changed, 147 insertions, 1 deletions
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index 49e0704bc..6d6341e4e 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -1,6 +1,7 @@
#include "slang-parser.h"
#include "../core/slang-semantic-version.h"
+#include "slang-ast-decl.h"
#include "slang-check-impl.h"
#include "slang-compiler.h"
#include "slang-lookup-spirv.h"
@@ -5123,9 +5124,103 @@ static bool parseGLSLGlobalDecl(Parser* parser, ContainerDecl* containerDecl)
return false;
}
+static void parseInterfaceDefaultMethodAsExplicitGeneric(
+ Parser* parser,
+ Decl* parsedDecl,
+ ContainerDecl* interfaceDecl)
+{
+ // If we parsed an interface method with a body,
+ // parse it again as an explicit generic decl on ThisType.
+ auto astBuilder = parser->astBuilder;
+ InterfaceDefaultImplDecl* genericDecl = astBuilder->create<InterfaceDefaultImplDecl>();
+ parser->PushScope(genericDecl);
+
+ // Create GenericTypeParamDecl for `This`.
+ auto thisTypeDecl = astBuilder->create<GenericTypeParamDecl>();
+ thisTypeDecl->nameAndLoc.name = getName(parser, "This");
+ thisTypeDecl->parameterIndex = 0;
+ genericDecl->thisTypeDecl = thisTypeDecl;
+ AddMember(genericDecl, thisTypeDecl);
+
+ // Create GenericTypeConstraintDecl for `This:IFoo`.
+ auto thisTypeConstraint = astBuilder->create<GenericTypeConstraintDecl>();
+ auto thisTypeVarExpr = astBuilder->create<VarExpr>();
+ thisTypeVarExpr->name = getName(parser, "This");
+ thisTypeVarExpr->scope = parser->currentScope;
+ thisTypeConstraint->sub.exp = thisTypeVarExpr;
+
+ // Since we can't form a DeclRef to the parent interface decl yet (because we are still parsing
+ // it), we will use a `ThisInterfaceExpr` here to represent the interface type.
+ auto thisInterfaceExpr = astBuilder->create<ThisInterfaceExpr>();
+ thisInterfaceExpr->scope = parser->currentScope;
+ thisTypeConstraint->sup.exp = thisInterfaceExpr;
+ genericDecl->thisTypeConstraintDecl = thisTypeConstraint;
+ AddMember(genericDecl, thisTypeConstraint);
+
+ parser->FillPosition(genericDecl);
+ parser->genericDepth++;
+ auto newInnerDecl = as<Decl>(ParseDecl(parser, genericDecl));
+ genericDecl->inner = newInnerDecl;
+ newInnerDecl->parentDecl = genericDecl;
+ parser->genericDepth--;
+ parser->PopScope();
+ AddMember(interfaceDecl, genericDecl);
+
+ // Mark the requirement decl with `HasInterfaceDefaultImplModifier`.
+ auto hasDefaultImplModifier = astBuilder->create<HasInterfaceDefaultImplModifier>();
+ hasDefaultImplModifier->defaultImplDecl = genericDecl;
+ addModifier(parsedDecl, hasDefaultImplModifier);
+
+ // Update the name of the generic and newly parsed func, to ensure
+ // the mangled name of the default impl func doesn't clash with the
+ // requirement.
+ StringBuilder sb;
+ sb << getText(newInnerDecl->getName()) << "$defaultImpl";
+ genericDecl->nameAndLoc.name = getName(parser, sb.produceString());
+
+ newInnerDecl->nameAndLoc.name = genericDecl->getName();
+ if (auto newInnerGenDecl = as<GenericDecl>(newInnerDecl))
+ {
+ // If the method itself is generic, continue to update the inner function's name.
+ if (newInnerGenDecl->inner)
+ {
+ newInnerGenDecl->inner->nameAndLoc.name = genericDecl->getName();
+ }
+ }
+
+ // Remove the body from the requirement decl.
+ auto requirementFunc = maybeGetInner(parsedDecl);
+ if (auto funcDecl = as<FuncDecl>(requirementFunc))
+ funcDecl->body = nullptr;
+}
+
+static void maybeReparseInterfaceFuncAsExplicitGeneric(
+ Parser* parser,
+ Decl* parsedDecl,
+ ContainerDecl* containerDecl,
+ TokenReader tokenReader)
+{
+ auto funcDecl = as<FuncDecl>(maybeGetInner(parsedDecl));
+ if (!funcDecl || !funcDecl->body)
+ return;
+
+ auto interfaceDecl = as<InterfaceDecl>(containerDecl);
+ if (!interfaceDecl)
+ return;
+
+ if (parser->sink->getErrorCount() != 0)
+ return;
+
+ Parser newParser(*parser);
+ newParser.tokenReader = tokenReader;
+ parseInterfaceDefaultMethodAsExplicitGeneric(&newParser, parsedDecl, interfaceDecl);
+}
+
static void parseDecls(Parser* parser, ContainerDecl* containerDecl, MatchedTokenType matchType)
{
+ TokenReader tokenReader;
Token closingBraceToken;
+ bool parentIsInterface = containerDecl->astNodeType == ASTNodeType::InterfaceDecl;
while (!AdvanceIfMatch(parser, matchType, &closingBraceToken))
{
if (parser->options.allowGLSLInput)
@@ -5133,7 +5228,58 @@ static void parseDecls(Parser* parser, ContainerDecl* containerDecl, MatchedToke
if (parseGLSLGlobalDecl(parser, containerDecl))
continue;
}
- ParseDecl(parser, containerDecl);
+ if (parentIsInterface)
+ tokenReader = parser->tokenReader;
+ auto decl = ParseDecl(parser, containerDecl);
+ if (parentIsInterface)
+ {
+ // If we parsed an interface method with a body (for the default implementation),
+ // we will create a duplicate decl where it is nested inside an explicit generic
+ // decl, such that `ThisType` is a generic type parameter.
+ // For an example, if the user writes:
+ // ```
+ // interface IFoo {
+ // int getVal();
+ // int getGreaterVal() { return getVal() + 1; }
+ // }
+ // ```
+ // We will represent `IFoo` as:
+ // ```
+ // interface IFoo {
+ // int getVal();
+ //
+ // [HasInterfaceDefaultImplModifier(getGreaterVal_defaultImpl)]
+ // int getGreaterVal();
+ //
+ // __interface_default_impl_generic<This:IFoo>
+ // int getGreaterVal_defaultImpl() {
+ // // `this` here will have type `This` (generic param).
+ // return this.getVal() + 1;
+ // }
+ // }
+ // ```
+ // Where `__interface_default_impl_generic` is a sub-class of `__generic`, and
+ // acts exactly like a generic. The sub-class is just to make it easy to
+ // identify a default impl. An interface default impl decl will not be treated
+ // as an interface requirement and lowered as an entry in the witness table,
+ // instead it is just an ordinary member decl that will be lowered into an
+ // `IRGeneric`.
+ //
+ // Ideally we would achieve the same result by parsing the function once, and
+ // then clone the AST nodes to represent `getGreaterVal_defaultImpl`, but we
+ // don't have the infrastructure to do that just yet. So we will play a dirty
+ // trick to achieve the same effect by re-parsing the method body as an
+ // explicit generic decl. Fortunately, this redundant parsing won't actually
+ // cause too much redundant work, because the function bodies are only parsed
+ // as an `UnparsedStmt` at this stage of parsing, which is a relatively simple
+ // process. Once we have the functionality to systematically clone AST nodes,
+ // we can eliminate this reparsing hack.
+ maybeReparseInterfaceFuncAsExplicitGeneric(
+ parser,
+ as<Decl>(decl),
+ containerDecl,
+ tokenReader);
+ }
}
containerDecl->closingSourceLoc = closingBraceToken.loc;
}