Skip to content

Commit

Permalink
Add diagnostic to verify the left hand side of a generic constraint.
Browse files Browse the repository at this point in the history
  • Loading branch information
csyonghe committed Sep 19, 2024
1 parent e904f0c commit b264399
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 4 deletions.
71 changes: 71 additions & 0 deletions source/slang/slang-check-decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ namespace Slang

void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl);

void validateGenericConstraintSubType(GenericTypeConstraintDecl* decl, TypeExp type);

void visitGenericDecl(GenericDecl* genericDecl);

void visitTypeDefDecl(TypeDefDecl* decl);
Expand Down Expand Up @@ -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<Decl>(type.type))
{
if (subDeclRef.getDecl()->parentDecl == decl->parentDecl)
{
// OK, sub type is one of the generic parameter type.
return;
}
if (as<GenericDecl>(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<T>() 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<AssocTypeDecl>(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<LookupDeclRef>(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<Type>(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?
Expand All @@ -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<ErrorType>(decl->sub.type))
{
getSink()->diagnose(decl->sup.exp, Diagnostics::invalidTypeForConstraint, decl->sup);
Expand Down
8 changes: 4 additions & 4 deletions source/slang/slang-check-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` depends on the generic decl that defines `T`.
//
DeclRef<GenericDecl> getDependentGenericParent(DeclRef<Decl> declRef);
private:
/// Mapping from type declarations to the known extensiosn that apply to them
Dictionary<AggTypeDecl*, RefPtr<CandidateExtensionList>> m_mapTypeDeclToCandidateExtensions;
Expand All @@ -766,10 +770,6 @@ namespace Slang
InheritanceInfo _calcInheritanceInfo(Type* type, InheritanceCircularityInfo* circularityInfo);
InheritanceInfo _calcInheritanceInfo(DeclRef<Decl> declRef, DeclRefType* correspondingType, InheritanceCircularityInfo* circularityInfo);

// Get the inner most generic decl that a decl-ref is dependent on.
// For example, `Foo<T>` depends on the generic decl that defines `T`.
//
DeclRef<GenericDecl> getDependentGenericParent(DeclRef<Decl> declRef);
void getDependentGenericParentImpl(DeclRef<GenericDecl>& genericParent, DeclRef<Decl> declRef);

struct DirectBaseInfo
Expand Down
1 change: 1 addition & 0 deletions source/slang/slang-diagnostic-defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
7 changes: 7 additions & 0 deletions tests/diagnostics/generic-constraint-left-hand-side.slang
Original file line number Diff line number Diff line change
@@ -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<T>() where int : IInteger
{}

0 comments on commit b264399

Please sign in to comment.