diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index c4f845b977..2307c16696 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -116,6 +116,8 @@ namespace Slang void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl); + void validateGenericConstraintSubType(GenericTypeConstraintDecl* decl, TypeExp type); + void visitGenericDecl(GenericDecl* genericDecl); void visitTypeDefDecl(TypeDefDecl* decl); @@ -2487,6 +2489,69 @@ namespace Slang return true; } + void SemanticsDeclHeaderVisitor::validateGenericConstraintSubType(GenericTypeConstraintDecl* decl, TypeExp type) + { + // Validate that the sub type of a constraint is in valid form. + // + if (auto subDeclRef = isDeclRefTypeOf(type.type)) + { + if (subDeclRef.getDecl()->parentDecl == decl->parentDecl) + { + // OK, sub type is one of the generic parameter type. + return; + } + if (as(decl->parentDecl)) + { + // If the constraint is in a generic decl, then the sub type must be dependent on at least one + // of the generic type parameters defined in the same generic decl. + // For example, it is invalid to define a constraint like `void foo() where int : float` since + // `int` isn't dependent on any generic type parameter. + auto dependentGeneric = getShared()->getDependentGenericParent(subDeclRef); + if (dependentGeneric.getDecl() != decl->parentDecl) + { + getSink()->diagnose(type.exp, Diagnostics::invalidConstraintSubType, type); + return; + } + } + else if (as(decl->parentDecl)) + { + // If the constraint is on an associated type, then it should either be the associated type itself, + // or a associated type of the associated type. + // For example, + // ``` + // interface IFoo { + // associatedtype T + // where T : IFoo // OK, constraint is on the associatedtype T itself. + // where T.T == X // OK, constraint is on the associated type of T. + // where int == X; // Error, int is not a valid left hand side of a constraint. + // } + // ``` + auto lookupDeclRef = as(subDeclRef.declRefBase); + if (!lookupDeclRef) + { + getSink()->diagnose(type.exp, Diagnostics::invalidConstraintSubType, type); + return; + } + + // We allow `associatedtype T where This.T : ...`. + // In this case, the left hand side will be in the form of + // LookupDeclRef(ThisType, T). i.e. lookupDeclRef->getDecl() == T. + // + if (lookupDeclRef->getDecl()->parentDecl == decl->parentDecl || + lookupDeclRef->getDecl() == decl->parentDecl) + return; + auto baseType = as(lookupDeclRef->getLookupSource()); + if (!baseType) + { + getSink()->diagnose(type.exp, Diagnostics::invalidConstraintSubType, type); + return; + } + type.type = baseType; + validateGenericConstraintSubType(decl, type); + } + } + } + void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl) { // TODO: are there any other validations we can do at this point? @@ -2501,6 +2566,12 @@ namespace Slang decl->sub = TranslateTypeNodeForced(decl->sub); if (!decl->sup.type) decl->sup = TranslateTypeNodeForced(decl->sup); + + if (getLinkage()->m_optionSet.shouldRunNonEssentialValidation()) + { + validateGenericConstraintSubType(decl, decl->sub); + } + if (!decl->isEqualityConstraint && !isValidGenericConstraintType(decl->sup) && !as(decl->sub.type)) { getSink()->diagnose(decl->sup.exp, Diagnostics::invalidTypeForConstraint, decl->sup); diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 8edcbb16a7..63c16e6d23 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -740,6 +740,10 @@ namespace Slang m_mapTypePairToImplicitCastMethod[key] = candidate; } + // Get the inner most generic decl that a decl-ref is dependent on. + // For example, `Foo` depends on the generic decl that defines `T`. + // + DeclRef getDependentGenericParent(DeclRef declRef); private: /// Mapping from type declarations to the known extensiosn that apply to them Dictionary> m_mapTypeDeclToCandidateExtensions; @@ -766,10 +770,6 @@ namespace Slang InheritanceInfo _calcInheritanceInfo(Type* type, InheritanceCircularityInfo* circularityInfo); InheritanceInfo _calcInheritanceInfo(DeclRef declRef, DeclRefType* correspondingType, InheritanceCircularityInfo* circularityInfo); - // Get the inner most generic decl that a decl-ref is dependent on. - // For example, `Foo` depends on the generic decl that defines `T`. - // - DeclRef getDependentGenericParent(DeclRef declRef); void getDependentGenericParentImpl(DeclRef& genericParent, DeclRef declRef); struct DirectBaseInfo diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index e0f1e90c5c..3a14441178 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -519,6 +519,7 @@ DIAGNOSTIC(39901, Fatal , cannotProcessInclude, "internal compiler error: cannot // 304xx: generics DIAGNOSTIC(30400, Error, genericTypeNeedsArgs, "generic type '$0' used without argument") DIAGNOSTIC(30401, Error, invalidTypeForConstraint, "type '$0' cannot be used as a constraint.") +DIAGNOSTIC(30402, Error, invalidConstraintSubType, "type '$0' is not a valid left hand side of a type constraint.") // 305xx: initializer lists DIAGNOSTIC(30500, Error, tooManyInitializers, "too many initializers (expected $0, got $1)") diff --git a/tests/diagnostics/generic-constraint-left-hand-side.slang b/tests/diagnostics/generic-constraint-left-hand-side.slang new file mode 100644 index 0000000000..61a5f6df3d --- /dev/null +++ b/tests/diagnostics/generic-constraint-left-hand-side.slang @@ -0,0 +1,7 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): + +// Check that we can issue diagnostic on invalid left hand side of a type constraint. + +// CHECK: ([[# @LINE+1]]): error 30402 +void f() where int : IInteger +{} \ No newline at end of file