diff options
| author | Yong He <yonghe@outlook.com> | 2025-06-13 22:13:00 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-13 22:13:00 -0700 |
| commit | 6a23949f07f4eba38086b656e7073ce3bf8cd2fe (patch) | |
| tree | 132bbe330b6027d323c74175686d006605e4da6d /source/slang | |
| parent | e72b3325663ab6d4bb791742574b031f0df6328a (diff) | |
Allow interface methods to have default implementations. (#7439)
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/slang-ast-decl.h | 8 | ||||
| -rw-r--r-- | source/slang/slang-ast-expr.h | 11 | ||||
| -rw-r--r-- | source/slang/slang-ast-iterator.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-ast-modifier.h | 8 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 131 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 29 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-language-server-ast-lookup.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-lookup.cpp | 29 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 76 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 148 | ||||
| -rw-r--r-- | source/slang/slang-syntax.h | 10 |
12 files changed, 417 insertions, 49 deletions
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index 3410806dc..c46878945 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -823,6 +823,14 @@ class GenericDecl : public ContainerDecl FIDDLE() Decl* inner = nullptr; }; +FIDDLE() +class InterfaceDefaultImplDecl : public GenericDecl +{ + FIDDLE(...) + FIDDLE() GenericTypeParamDecl* thisTypeDecl; + FIDDLE() GenericTypeConstraintDecl* thisTypeConstraintDecl; +}; + FIDDLE(abstract) class GenericTypeParamDeclBase : public SimpleTypeDecl { diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index 950fd6645..177feff15 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -734,6 +734,17 @@ class ThisTypeExpr : public Expr Scope* scope = nullptr; }; + +/// A type expression of the form `ThisInterface` +/// +/// Refers to the interface type itself, not the conforming type from an interface decl. +/// +FIDDLE() +class ThisInterfaceExpr : public VarExpr +{ + FIDDLE(...) +}; + /// A type expression of the form `Left & Right`. FIDDLE() class AndTypeExpr : public Expr diff --git a/source/slang/slang-ast-iterator.h b/source/slang/slang-ast-iterator.h index 4c866fc5a..094a9c1a2 100644 --- a/source/slang/slang-ast-iterator.h +++ b/source/slang/slang-ast-iterator.h @@ -245,6 +245,10 @@ struct ASTIterator void visitThisExpr(ThisExpr* expr) { iterator->maybeDispatchCallback(expr); } void visitThisTypeExpr(ThisTypeExpr* expr) { iterator->maybeDispatchCallback(expr); } + void visitThisInterfaceExpr(ThisInterfaceExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } void visitReturnValExpr(ReturnValExpr* expr) { iterator->maybeDispatchCallback(expr); } void visitAndTypeExpr(AndTypeExpr* expr) diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h index 10ebc2841..4f6a08e4f 100644 --- a/source/slang/slang-ast-modifier.h +++ b/source/slang/slang-ast-modifier.h @@ -731,6 +731,14 @@ class HLSLVolatileModifier : public Modifier FIDDLE(...) }; +// Indicate that an interface method requirement has a default impl. +FIDDLE() +class HasInterfaceDefaultImplModifier : public Modifier +{ + FIDDLE(...) +public: + FIDDLE() Decl* defaultImplDecl = nullptr; +}; FIDDLE() class AttributeTargetModifier : public Modifier diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index ddc4ec4d5..8471acb0a 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -738,6 +738,7 @@ struct SemanticsDeclReferenceVisitor : public SemanticsDeclVisitorBase, void visitThisExpr(ThisExpr*) { return; } void visitThisTypeExpr(ThisTypeExpr*) { return; } + void visitThisInterfaceExpr(ThisInterfaceExpr*) { return; } void visitAndTypeExpr(AndTypeExpr* expr) { dispatchIfNotNull(expr->left.type); @@ -5043,6 +5044,17 @@ static void removeNonStaticLookupItems(LookupResult& lookupResult) } } +// Returns true if declRef points to an interface method requirement that has a default +// implementation. +HasInterfaceDefaultImplModifier* hasDefaultImpl(DeclRef<Decl> declRef) +{ + if (auto modifier = declRef.getDecl()->findModifier<HasInterfaceDefaultImplModifier>()) + return modifier; + if (auto genericParent = as<GenericDecl>(declRef.getDecl()->parentDecl)) + return genericParent->findModifier<HasInterfaceDefaultImplModifier>(); + return nullptr; +} + bool SemanticsVisitor::trySynthesizeMethodRequirementWitness( ConformanceCheckingContext* context, LookupResult const& lookupResult, @@ -5091,17 +5103,19 @@ bool SemanticsVisitor::trySynthesizeMethodRequirementWitness( // With the big picture spelled out, we can settle into // the work of constructing our synthesized method. // - bool isInWrapperType = isWrapperTypeDecl(context->parentDecl); // First, we check that the differentiabliity of the method matches the requirement, // and we don't attempt to synthesize a method if they don't match. - if (!isInWrapperType && getShared()->getFuncDifferentiableLevel( - as<FunctionDeclBase>(lookupResult.item.declRef.getDecl())) < - getShared()->getFuncDifferentiableLevel( - as<FunctionDeclBase>(requiredMemberDeclRef.getDecl()))) + if (lookupResult.isValid()) { - return false; + if (!isInWrapperType && getShared()->getFuncDifferentiableLevel( + as<FunctionDeclBase>(lookupResult.item.declRef.getDecl())) < + getShared()->getFuncDifferentiableLevel( + as<FunctionDeclBase>(requiredMemberDeclRef.getDecl()))) + { + return false; + } } ThisExpr* synThis = nullptr; @@ -6899,6 +6913,95 @@ bool SemanticsVisitor::trySynthesizeDifferentialMethodRequirementWitness( return true; } +bool SemanticsVisitor::findDefaultInterfaceImpl( + ConformanceCheckingContext* context, + DeclRef<Decl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable) +{ + // Only functions can have default implemnetation at the moment. + DeclRef<FuncDecl> requiredFuncDeclRef = requiredMemberDeclRef.as<FuncDecl>(); + if (!requiredFuncDeclRef) + { + // If requiredMember is a generic func, form a direct declref to the inner func. + if (auto requiredGenericDeclRef = requiredMemberDeclRef.as<GenericDecl>()) + { + auto inner = getInner(requiredGenericDeclRef); + if (auto func = as<FuncDecl>(inner)) + { + requiredFuncDeclRef = m_astBuilder->getMemberDeclRef(requiredGenericDeclRef, func); + } + } + } + if (!requiredFuncDeclRef) + return false; + + // If the interface requirement comes with a default impl, it should have a + // HasInterfaceDefaultImplModifier. + auto defaultModifier = hasDefaultImpl(requiredMemberDeclRef); + if (!defaultModifier) + return false; + + // If we have a default implementation, return a declref to the default stub decl directly. + // We can grab a declref to the stub decl standing for the generic default impl from + // the InterfaceDefaultImplModifier on the requiredMemberDeclRef, that should have been + // created during checking of the required method decl. + // + // For example, given: + // ``` + // interface IFoo { int bar() { return 1; } } + // struct Foo : IFoo { /*needing witness synthesis*/ } + // ``` + // After parsing `IFoo`, we will have a default impl stub decl for `bar`: + // ``` + // interface IFoo { + // [HasInterfaceDefaultImpl(bar_defaultImpl)] // <-- `defaultModifier` + // int bar(); // this is the requirement. + // + // // This is the default impl stub decl. + // __interface_default_impl_generic<This:IFoo> + // int bar_defaultImpl() { return 1; } + // } + // ``` + // Given this, we can simply form a GenericAppDeclRef to `IFoo::bar_defaultImpl` + // with `This` being `context->conformingType` and `This:IFoo` being + // `context->conformingWitness`. + // + // The following logic will create this declref, and register it to the witness table. + // + auto interfaceDeclRef = getParentDeclRef(requiredMemberDeclRef); + if (!as<InterfaceDecl>(interfaceDeclRef.getDecl())) + return false; + + auto genericDeclOfDefaultImplStub = + as<InterfaceDefaultImplDecl>(defaultModifier->defaultImplDecl); + if (!genericDeclOfDefaultImplStub) + return false; + + // Form a declref to unspecialized `IFoo::bar_defaultImpl`. + DeclRef<Decl> resultDeclRef = + m_astBuilder->getMemberDeclRef(interfaceDeclRef, genericDeclOfDefaultImplStub); + List<Val*> specArgs; + specArgs.add(context->conformingType); + specArgs.add(context->conformingWitness); + + // Form a declref to `IFoo::bar_defaultImpl<conformingType>`. + resultDeclRef = m_astBuilder->getGenericAppDeclRef( + resultDeclRef.as<GenericDecl>(), + specArgs.getArrayView()); + + // If `bar_defaultImpl` is a generic method, we need to form an explicit + // declref to the inner method decl to stay consistent the existing format of + // witness table entries. + if (auto genDeclRef = as<GenericDecl>(resultDeclRef)) + resultDeclRef = m_astBuilder->getMemberDeclRef(genDeclRef, genDeclRef.getDecl()->inner); + + // Register the declref to the witness table. + auto callableDeclRef = as<CallableDecl>(resultDeclRef); + SLANG_ASSERT(callableDeclRef); + _addMethodWitness(witnessTable, requiredFuncDeclRef, callableDeclRef); + return true; +} + bool SemanticsVisitor::findWitnessForInterfaceRequirement( ConformanceCheckingContext* context, Type* subType, @@ -7043,7 +7146,8 @@ bool SemanticsVisitor::findWitnessForInterfaceRequirement( { // If we failed to look up a member with the name of the // requirement, it may be possible that we can still synthesis the - // implementation if this is one of the known builtin requirements. + // implementation if this is one of the known builtin requirements, + // or if the interface method contains a default impl. // Otherwise, report diagnostic now. if (requiredMemberDeclRef.getDecl()->hasModifier<BuiltinRequirementModifier>() || @@ -7059,6 +7163,9 @@ bool SemanticsVisitor::findWitnessForInterfaceRequirement( as<MatrixExpressionType>(context->conformingType))) { } + else if (hasDefaultImpl(requiredMemberDeclRef)) + { + } else { getSink()->diagnose( @@ -7147,6 +7254,12 @@ bool SemanticsVisitor::findWitnessForInterfaceRequirement( return true; } + // Finally, if there is a default implementation for the required member, + // we can use that as a witness. + // + if (findDefaultInterfaceImpl(context, requiredMemberDeclRef, witnessTable)) + return true; + // We failed to find a member of the type that can be used // to satisfy the requirement (even via synthesis), so we // need to report the failure to the user. @@ -7312,6 +7425,8 @@ bool SemanticsVisitor::checkInterfaceConformance( continue; if (requiredMemberDecl.as<DerivativeRequirementDecl>()) continue; + if (as<InterfaceDefaultImplDecl>(requiredMemberDecl.getDecl())) + continue; ensureDecl(requiredMemberDecl, DeclCheckState::ReadyForReference); auto requiredMemberDeclRef = m_astBuilder->getLookupDeclRef( subTypeConformsToSuperInterfaceWitness, @@ -7512,7 +7627,7 @@ bool SemanticsVisitor::checkConformance( ConformanceCheckingContext context; context.conformingType = subType; context.parentDecl = parentDecl; - + context.conformingWitness = subIsSuperWitness; RefPtr<WitnessTable> witnessTable = inheritanceDecl->witnessTable; if (!witnessTable) diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 9f5c1dc2e..4c6bf98d2 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -5346,6 +5346,12 @@ Expr* SemanticsExprVisitor::visitThisExpr(ThisExpr* expr) } return expr; } + else if (auto defaultImplDecl = as<InterfaceDefaultImplDecl>(containerDecl)) + { + expr->type.type = + DeclRefType::create(m_astBuilder, DeclRef<Decl>(defaultImplDecl->thisTypeDecl)); + return expr; + } #if 0 else if (auto aggTypeDecl = as<AggTypeDecl>(containerDecl)) { @@ -5401,7 +5407,12 @@ Expr* SemanticsExprVisitor::visitThisTypeExpr(ThisTypeExpr* expr) expr->type.type = thisTypeType; return expr; } - + else if (auto defaultImplDecl = as<InterfaceDefaultImplDecl>(containerDecl)) + { + expr->type.type = + DeclRefType::create(m_astBuilder, DeclRef<Decl>(defaultImplDecl->thisTypeDecl)); + return expr; + } scope = scope->parent; } @@ -5409,6 +5420,22 @@ Expr* SemanticsExprVisitor::visitThisTypeExpr(ThisTypeExpr* expr) return CreateErrorExpr(expr); } +Expr* SemanticsExprVisitor::visitThisInterfaceExpr(ThisInterfaceExpr* expr) +{ + auto scope = expr->scope; + + auto containerDecl = findParentInterfaceDecl(scope->containerDecl); + + // ThisInterfaceExpr can only be synthesized by the compiler during parsing + // an interface decl with default implementation, so container must always + // be an interface decl. + SLANG_ASSERT(containerDecl); + expr->declRef = + createDefaultSubstitutionsIfNeeded(m_astBuilder, this, getDefaultDeclRef(containerDecl)); + expr->type = m_astBuilder->getTypeType(DeclRefType::create(m_astBuilder, expr->declRef)); + return expr; +} + Expr* SemanticsExprVisitor::visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr) { // CastToSuperType is effectively a struct field. diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 1cdebb115..be6b1c2fc 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1837,6 +1837,8 @@ public: /// The type for which conformances are being checked Type* conformingType; + Witness* conformingWitness; + /// The outer declaration for the conformances being checked (either a type or `extension` /// declaration) ContainerDecl* parentDecl; @@ -2027,6 +2029,13 @@ public: // Check and register a type if it is differentiable. void maybeRegisterDifferentiableType(ASTBuilder* builder, Type* type); + // Find the default implementation of an interface requirement, + // and insert it to the witness table, if it exists. + bool findDefaultInterfaceImpl( + ConformanceCheckingContext* context, + DeclRef<Decl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable); + // Find the appropriate member of a declared type to // satisfy a requirement of an interface the type // claims to conform to. @@ -2989,6 +2998,7 @@ public: Expr* visitThisExpr(ThisExpr* expr); Expr* visitThisTypeExpr(ThisTypeExpr* expr); + Expr* visitThisInterfaceExpr(ThisInterfaceExpr* expr); Expr* visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr); Expr* visitReturnValExpr(ReturnValExpr* expr); Expr* visitAndTypeExpr(AndTypeExpr* expr); diff --git a/source/slang/slang-language-server-ast-lookup.cpp b/source/slang/slang-language-server-ast-lookup.cpp index 6c5935992..f394e7d7a 100644 --- a/source/slang/slang-language-server-ast-lookup.cpp +++ b/source/slang/slang-language-server-ast-lookup.cpp @@ -420,6 +420,8 @@ public: } bool visitThisTypeExpr(ThisTypeExpr*) { return false; } + bool visitThisInterfaceExpr(ThisInterfaceExpr*) { return false; } + bool visitAndTypeExpr(AndTypeExpr* expr) { if (dispatchIfNotNull(expr->left.exp)) diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index d03d763b8..604cee2b7 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -977,6 +977,35 @@ static void _lookUpInScopes( nullptr); } + if (auto defaultImplDecl = as<InterfaceDefaultImplDecl>(containerDecl)) + { + // If we are checking an interface default method implementation, + // we should look up members from implicit `this` whose type is the explicit `This` + // generic parameter, and skip looking up in the interface decl itself. + + // Instead of looking up in the interface decl itself, we should + // look up in the `This` type instead. + if (getText(name) != "This") + { + BreadcrumbInfo breadcrumb; + breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::This; + breadcrumb.thisParameterMode = thisParameterMode; + breadcrumb.declRef = DeclRef<Decl>(defaultImplDecl->thisTypeDecl); + breadcrumb.prev = nullptr; + Type* type = DeclRefType::create(astBuilder, breadcrumb.declRef); + _lookUpMembersInType(astBuilder, name, type, request, result, &breadcrumb); + } + + // We need to skip looking up in the interface decl itself, since we are + // looking up in the implicit `this` type. + for (; scope && !as<InterfaceDecl>(scope->containerDecl); scope = scope->parent) + { + // We need to skip looking up in the interface decl itself, since we are + // looking up in the implicit `this` type. + } + break; + } + // Before we proceed up to the next outer scope to perform lookup // again, we need to consider what the current scope tells us // about how to interpret uses of implicit `this` or `This`. For diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 596a091d4..a03c75d7d 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -1495,6 +1495,15 @@ bool shouldDeclBeTreatedAsInterfaceRequirement(Decl* requirementDecl) else if (const auto varDecl = as<VarDeclBase>(requirementDecl)) { } + else if (as<AccessorDecl>(requirementDecl)) + { + } + else if (as<InterfaceDefaultImplDecl>(requirementDecl)) + { + // A default impl stub function represents a concrete function, not + // a requirement. + return false; + } else if (const auto genericDecl = as<GenericDecl>(requirementDecl)) { return shouldDeclBeTreatedAsInterfaceRequirement(genericDecl->inner); @@ -1511,6 +1520,11 @@ bool shouldDeclBeTreatedAsInterfaceRequirement(Decl* requirementDecl) IRStructKey* getInterfaceRequirementKey(IRGenContext* context, Decl* requirementDecl) { + // Only specific types of decls are treated as requirements, e.g. methods and asssociated types. + // Other types of decls are allowed but not regarded as a requirement. + if (!shouldDeclBeTreatedAsInterfaceRequirement(requirementDecl)) + return nullptr; + // TODO: this special case logic can be removed if we also clean up // `doesGenericSignatureMatchRequirement` Currently `doesGenericSignatureMatchRequirement` will // use the inner func decl as the key in AST WitnessTable. Therefore we need to match this @@ -1518,11 +1532,6 @@ IRStructKey* getInterfaceRequirementKey(IRGenContext* context, Decl* requirement if (auto genericDecl = as<GenericDecl>(requirementDecl)) return getInterfaceRequirementKey(context, genericDecl->inner); - // Only specific types of decls are treated as requirements, e.g. methods and asssociated types. - // Other types of decls are allowed but not regarded as a requirement. - if (!shouldDeclBeTreatedAsInterfaceRequirement(requirementDecl)) - return nullptr; - IRStructKey* requirementKey = nullptr; if (context->shared->interfaceRequirementKeys.tryGetValue(requirementDecl, requirementKey)) { @@ -3046,6 +3055,14 @@ static Type* _findReplacementThisParamType(IRGenContext* context, DeclRef<Decl> return thisType; } + if (auto defaultImplDeclRef = parentDeclRef.as<InterfaceDefaultImplDecl>()) + { + auto thisType = DeclRefType::create( + context->astBuilder, + DeclRef<Decl>(defaultImplDeclRef.getDecl()->thisTypeDecl)); + return thisType; + } + return nullptr; } @@ -3257,6 +3274,10 @@ void collectParameterLists( ParameterListCollectMode mode, ParameterDirection thisParamDirection) { + // Don't collect any parameters beyond certain decls. + if (as<InterfaceDefaultImplDecl>(declRef) || as<AggTypeDeclBase>(declRef)) + return; + // The parameters introduced by any "parent" declarations // will need to come first, so we'll deal with that // logic here. @@ -9221,28 +9242,29 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> UInt operandCount = 0; for (auto requirementDecl : decl->getDirectMemberDecls()) { + auto innerRequirementDecl = requirementDecl; + if (as<InterfaceDefaultImplDecl>(requirementDecl)) + continue; if (as<GenericDecl>(requirementDecl)) - requirementDecl = getInner(requirementDecl); + innerRequirementDecl = getInner(requirementDecl); - if (as<SubscriptDecl>(requirementDecl) || as<PropertyDecl>(requirementDecl)) + if (as<SubscriptDecl>(innerRequirementDecl) || as<PropertyDecl>(innerRequirementDecl)) { - for (auto accessorDecl : - as<ContainerDecl>(requirementDecl)->getDirectMemberDeclsOfType<AccessorDecl>()) + for (auto accessorDecl : as<ContainerDecl>(innerRequirementDecl) + ->getDirectMemberDeclsOfType<AccessorDecl>()) { SLANG_UNUSED(accessorDecl); operandCount++; } } if (!shouldDeclBeTreatedAsInterfaceRequirement(requirementDecl)) - { continue; - } operandCount++; // As a special case, any type constraints placed // on an associated type will *also* need to be turned // into requirement keys for this interface. - if (auto associatedTypeDecl = as<AssocTypeDecl>(requirementDecl)) + if (auto associatedTypeDecl = as<AssocTypeDecl>(innerRequirementDecl)) { operandCount += associatedTypeDecl->getMembersOfType<TypeConstraintDecl>().getCount(); @@ -9374,6 +9396,8 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> auto requirementKey = getInterfaceRequirementKey(requirementDecl); if (!requirementKey) { + if (as<InterfaceDefaultImplDecl>(requirementDecl)) + continue; if (auto genericDecl = as<GenericDecl>(requirementDecl)) { // We need to form a declref into the inner decls in case of a generic @@ -11361,33 +11385,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> LoweredValInfo visitGenericDecl(GenericDecl* genDecl) { - // TODO: Should this just always visit/lower the inner decl? - - if (auto innerFuncDecl = as<FunctionDeclBase>(genDecl->inner)) - return ensureDecl(context, innerFuncDecl); - else if (auto innerStructDecl = as<StructDecl>(genDecl->inner)) - { - ensureDecl(context, innerStructDecl); - return LoweredValInfo(); - } - else if (auto extensionDecl = as<ExtensionDecl>(genDecl->inner)) - { - return ensureDecl(context, extensionDecl); - } - else if (auto interfaceDecl = as<InterfaceDecl>(genDecl->inner)) - { - return ensureDecl(context, interfaceDecl); - } - else if (auto typedefDecl = as<TypeDefDecl>(genDecl->inner)) - { - return ensureDecl(context, typedefDecl); - } - else if (auto subscriptDecl = as<SubscriptDecl>(genDecl->inner)) - { - return ensureDecl(context, subscriptDecl); - } - SLANG_RELEASE_ASSERT(false); - UNREACHABLE_RETURN(LoweredValInfo()); + return ensureDecl(context, genDecl->inner); } LoweredValInfo visitFunctionDeclBase(FunctionDeclBase* decl) 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; } diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h index 1b5fc005d..e7a82592c 100644 --- a/source/slang/slang-syntax.h +++ b/source/slang/slang-syntax.h @@ -284,6 +284,14 @@ inline Decl* getInner(DeclRef<GenericDecl> declRef) return declRef.getDecl()->inner; } +inline Decl* maybeGetInner(Decl* decl) +{ + if (auto genericDeclRef = as<GenericDecl>(decl)) + { + return genericDeclRef->inner; + } + return decl; +} // inline Type* getType(ASTBuilder* astBuilder, SubstExpr<Expr> expr) @@ -393,6 +401,8 @@ AggTypeDecl* getParentAggTypeDecl(Decl* decl); AggTypeDeclBase* getParentAggTypeDeclBase(Decl* decl); FunctionDeclBase* getParentFunc(Decl* decl); +/// Get the parent declref, skipping any generic decls in between. +DeclRef<Decl> getParentDeclRef(DeclRef<Decl> declRef); } // namespace Slang #endif |
