From 2c4bfce49d9af2414f6a3f70f7221d6890a017e7 Mon Sep 17 00:00:00 2001 From: sricker-nvidia <115114531+sricker-nvidia@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:15:51 -0700 Subject: Add error for forward references in generic constraints (#7615) * Add error for forward references in generic constraints Change addresses issue #6545. The slang compiler's type checker is unable to support cases where a generic type parameter is referenced as a constraint before it is declared. For example code like: ```` interface IFoo { } void bar, T : IFloat>() { } ```` Is not supported, but will currently report a generic error like, "(0): error 99999: Slang compilation aborted due to an exception of class Slang::InternalError: unexpected: generic type constraint during lowering" This change adds a new check for this kind of code and reports an error like, "error 30117: generic constraint for parameter 'Foo' references type parameter 'T' before it is declared" when detected. Basic testing of this error is also added in a new diagnostic test. * Add error for forward refs in generic constiants update 1 * Add error for forward refs in generic constraints update 2 Revised algo in checkForwardReferencesInGenericConstraint to run in O(n) instead of the previous O(n^2) using HashSets. * Add error for forward refs in generic constraints update 3 -Update logic adding referenced decl's in collectReferencedDecls. -Simplified error string logic. * Add error for forward refs in generic constraints update 4 -Declare collectReferencedDecls in slang-check.h such that it can be used as a general utility function. * format code --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> --- source/slang/slang-check-decl.cpp | 103 +++++++++++++++++++++++++++++++++++ source/slang/slang-check.h | 3 + source/slang/slang-diagnostic-defs.h | 5 ++ 3 files changed, 111 insertions(+) (limited to 'source') diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 9c77303b9..558834c34 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -343,6 +343,8 @@ struct SemanticsDeclHeaderVisitor : public SemanticsDeclVisitorBase, void validateGenericConstraintSubType(GenericTypeConstraintDecl* decl, TypeExp type); + void checkForwardReferencesInGenericConstraint(GenericTypeConstraintDecl* decl); + void visitGenericDecl(GenericDecl* genericDecl); void visitTypeDefDecl(TypeDefDecl* decl); @@ -3220,6 +3222,104 @@ void SemanticsDeclHeaderVisitor::validateGenericConstraintSubType( } } +// General utility function to collect all referenced declarations from a value +void collectReferencedDecls(Val* val, HashSet& outDecls) +{ + if (!val) + return; + + // Process operands to find declaration references + for (Index i = 0; i < val->getOperandCount(); i++) + { + auto& operand = val->m_operands[i]; + if (operand.kind == ValNodeOperandKind::ValNode) + { + // ValNode operands contain Val* nodes that we recursively + // traverse to find nested declaration references. For example, in the + // constraint expression IFoo>, we need to traverse the nested + // type structure to find the reference to declaration T. + collectReferencedDecls(val->getOperand(i), outDecls); + } + else if (operand.kind == ValNodeOperandKind::ASTNode) + { + // ASTNode operands are leaf cases. They can contain any NodeBase*, + // so we need to check if the referenced astnode is actually a Decl*. + if (auto declOperand = as(operand.values.nodeOperand)) + { + outDecls.add(declOperand); + } + } + } +} + +void SemanticsDeclHeaderVisitor::checkForwardReferencesInGenericConstraint( + GenericTypeConstraintDecl* decl) +{ + // Check if this constraint references type parameters that appear later + // in the same GenericDecl's parameter list and report a forward reference error + // if it does. + + // Only applies to constraints within GenericDecl contexts where declaration order matters. + // If that's not the case, then early out. + auto parentGeneric = as(decl->parentDecl); + if (!parentGeneric) + return; + + // Build a HashSet of all generic parameter declarations seen before the constraint. + // After we collect the referenced declarations from the constraint's superior type, + // we can do a quick check with the HashSet to see if a reference falls outside of + // the set, indicating that we have a forward reference. + HashSet declaredBeforeConstraint; + bool foundConstraint = false; + + for (Index i = 0; i < parentGeneric->getDirectMemberDeclCount(); ++i) + { + auto member = parentGeneric->getDirectMemberDecl(i); + + if (member == decl) + { + foundConstraint = true; + break; + } + + // Add generic type parameters to our "declared so far" set + if (auto typeParam = as(member)) + { + declaredBeforeConstraint.add(typeParam); + } + } + + // This probably shouldn't happen, but if the constraint is not found in parent GenericDecl, + // just early out as we can't check forward references. + if (!foundConstraint) + return; + + // Collect all referenced declarations from the constraint's superior type + HashSet referencedDecls; + collectReferencedDecls(decl->sup.type, referencedDecls); + + // Check if any of the referenced declarations are forward references (not in our "declared so + // far" set) + for (auto referencedDecl : referencedDecls) + { + if (auto typeParam = as(referencedDecl)) + { + // Check if this type parameter belongs to the same generic but is NOT in our "declared + // so far" set + if (typeParam->parentDecl == parentGeneric && + !declaredBeforeConstraint.contains(typeParam)) + { + // Found a forward reference, report an error. + getSink()->diagnose( + decl->sup.exp, + Diagnostics::forwardReferenceInGenericConstraint, + decl->sub.type, + typeParam); + } + } + } +} + void SemanticsDeclHeaderVisitor::visitTypeCoercionConstraintDecl(TypeCoercionConstraintDecl* decl) { CheckConstraintSubType(decl->toType); @@ -3245,6 +3345,9 @@ void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(GenericTypeConst if (!decl->sup.type) decl->sup = TranslateTypeNodeForced(decl->sup); + // Check for forward references in generic constraints after type translation + checkForwardReferencesInGenericConstraint(decl); + if (getLinkage()->m_optionSet.shouldRunNonEssentialValidation()) { validateGenericConstraintSubType(decl, decl->sub); diff --git a/source/slang/slang-check.h b/source/slang/slang-check.h index ebeff1afe..118b08a83 100644 --- a/source/slang/slang-check.h +++ b/source/slang/slang-check.h @@ -37,4 +37,7 @@ OrderedDictionary> getCanonicalGenericCon OrderedDictionary> getCanonicalGenericConstraints2( ASTBuilder* builder, DeclRef genericDecl); + +// General utility function to collect all referenced declarations from a value +void collectReferencedDecls(Val* val, HashSet& outDecls); } // namespace Slang diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 8efdf1d91..bf0e91150 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -1054,6 +1054,11 @@ DIAGNOSTIC( Error, throwTypeIncompatibleWithErrorType, "the type `$0` of `throw` is not compatible with function's error type `$1`.") +DIAGNOSTIC( + 30117, + Error, + forwardReferenceInGenericConstraint, + "generic constraint for parameter '$0' references type parameter '$1' before it is declared") // Include DIAGNOSTIC( -- cgit v1.2.3