summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2025-07-10 21:14:16 -0700
committerGitHub <noreply@github.com>2025-07-11 04:14:16 +0000
commit90c34e3db4fdc7be79c62bd91905a2a84bbd673e (patch)
treea8ccf3c2dbfe5f1faa646bf91e41d9a12a66c804 /source/slang
parent7764b83d24d341334ca7c1693cae2472be8f8d99 (diff)
Ensure generic constraints are checked before inner extension. (#7685)
* Ensure generic constraints are checked before inner extension. * Add warning for non-standard generic extension. * Fix tests. * Fix test. * Ban interface types from equality constraints. * Fix.
Diffstat (limited to 'source/slang')
-rw-r--r--source/slang/slang-check-decl.cpp98
-rw-r--r--source/slang/slang-check-impl.h2
-rw-r--r--source/slang/slang-diagnostic-defs.h12
-rw-r--r--source/slang/slang-parser.cpp2
4 files changed, 93 insertions, 21 deletions
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index 7cd1d7df8..e67962ca3 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -1414,6 +1414,20 @@ static void _dispatchDeclCheckingVisitor(
DeclCheckState state,
SemanticsContext& shared);
+void SemanticsVisitor::ensureOuterGenericConstraints(Decl* decl, DeclCheckState state)
+{
+ if (auto genericDecl = GetOuterGeneric(decl))
+ {
+ auto nextGeneric = findNextOuterGeneric(genericDecl);
+ if (nextGeneric)
+ {
+ if (nextGeneric->checkState.getState() < state)
+ ensureOuterGenericConstraints(nextGeneric, state);
+ }
+ ensureDecl(genericDecl, state);
+ }
+}
+
// Make sure a declaration has been checked, so we can refer to it.
// Note that this may lead to us recursively invoking checking,
// so this may not be the best way to handle things.
@@ -3159,6 +3173,18 @@ bool SemanticsVisitor::trySynthesizeDifferentialAssociatedTypeRequirementWitness
return true;
}
+bool isProperConstraineeType(Type* type)
+{
+ auto declRef = isDeclRefTypeOf<Decl>(type);
+ if (!declRef)
+ return false;
+ if (as<InterfaceDecl>(declRef.getDecl()))
+ return false;
+ // TODO: `some` type and `dyn` types are also inproper constrainee types.
+
+ return true;
+}
+
void SemanticsDeclHeaderVisitor::validateGenericConstraintSubType(
GenericTypeConstraintDecl* decl,
TypeExp type)
@@ -3221,6 +3247,13 @@ void SemanticsDeclHeaderVisitor::validateGenericConstraintSubType(
validateGenericConstraintSubType(decl, type);
}
}
+ if (!isProperConstraineeType(type.type))
+ {
+ // It is meaningless for certain types to be used in type constraints.
+ // For example, `IFoo<T>` should not appear as the left-hand-side of a generic constraint.
+ getSink()->diagnose(type.exp, Diagnostics::invalidConstraintSubType, type);
+ return;
+ }
}
// General utility function to collect all referenced declarations from a value
@@ -3333,12 +3366,6 @@ void SemanticsDeclHeaderVisitor::visitTypeCoercionConstraintDecl(TypeCoercionCon
void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl)
{
- // TODO: are there any other validations we can do at this point?
- //
- // There probably needs to be a kind of "occurs check" to make
- // sure that the constraint actually applies to at least one
- // of the parameters of the generic.
- //
CheckConstraintSubType(decl->sub);
if (!decl->sub.type)
@@ -3346,18 +3373,32 @@ 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);
- }
+ // Check for forward references in generic constraints after type translation
+ checkForwardReferencesInGenericConstraint(decl);
- if (!decl->isEqualityConstraint && !isValidGenericConstraintType(decl->sup) &&
- !as<ErrorType>(decl->sub.type))
- {
- getSink()->diagnose(decl->sup.exp, Diagnostics::invalidTypeForConstraint, decl->sup);
+ validateGenericConstraintSubType(decl, decl->sub);
+ if (decl->isEqualityConstraint)
+ {
+ if (!isProperConstraineeType(decl->sup) && !as<ErrorType>(decl->sup.type))
+ {
+ getSink()->diagnose(
+ decl->sup.exp,
+ Diagnostics::invalidEqualityConstraintSupType,
+ decl->sup);
+ }
+ }
+ else
+ {
+ if (!isValidGenericConstraintType(decl->sup) && !as<ErrorType>(decl->sup.type))
+ {
+ getSink()->diagnose(
+ decl->sup.exp,
+ Diagnostics::invalidTypeForConstraint,
+ decl->sup);
+ }
+ }
}
}
@@ -10708,14 +10749,18 @@ void SemanticsDeclBasesVisitor::_validateExtensionDeclGenericParams(ExtensionDec
ensureDecl(genericDecl, DeclCheckState::ReadyForReference);
// Collect all declarations referenced by the target type
- HashSet<Decl*> referencedDecls;
- collectReferencedDecls(decl->targetType.type, referencedDecls);
+ HashSet<Decl*> genericParamsReferencedByTargetType;
+ collectReferencedDecls(decl->targetType.type, genericParamsReferencedByTargetType);
+
+ HashSet<Decl*> genericParamsReferencedByConstraints;
// Also collect declarations referenced by generic constraints
for (auto constraint :
getMembersOfType<GenericTypeConstraintDecl>(getASTBuilder(), genericDecl))
{
- collectReferencedDecls(constraint.getDecl()->sup.type, referencedDecls);
+ collectReferencedDecls(
+ constraint.getDecl()->sup.type,
+ genericParamsReferencedByConstraints);
}
// Note: We intentionally do NOT check inheritance declarations in the extension.
@@ -10727,7 +10772,9 @@ void SemanticsDeclBasesVisitor::_validateExtensionDeclGenericParams(ExtensionDec
{
if (as<GenericTypeParamDeclBase>(member) || as<GenericValueParamDecl>(member))
{
- if (!referencedDecls.contains(member))
+ bool referencedByTargetType = genericParamsReferencedByTargetType.contains(member);
+ bool referencedByConstraint = genericParamsReferencedByConstraints.contains(member);
+ if (!referencedByTargetType && !referencedByConstraint)
{
getSink()->diagnose(
member,
@@ -10735,6 +10782,14 @@ void SemanticsDeclBasesVisitor::_validateExtensionDeclGenericParams(ExtensionDec
member->getName(),
decl->targetType);
}
+ else if (!referencedByTargetType && !isFromCoreModule(decl))
+ {
+ getSink()->diagnose(
+ member,
+ Diagnostics::genericParamInExtensionNotReferencedByTargetType,
+ member->getName(),
+ decl->targetType);
+ }
}
}
}
@@ -10742,6 +10797,11 @@ void SemanticsDeclBasesVisitor::_validateExtensionDeclGenericParams(ExtensionDec
void SemanticsDeclBasesVisitor::visitExtensionDecl(ExtensionDecl* decl)
{
+ // If the extension is a generic, we need to make sure to check
+ // the outer generic constraints first.
+ //
+ ensureOuterGenericConstraints(decl, DeclCheckState::ReadyForReference);
+
// We check the target type expression and members, and then validate
// that the type it names is one that it makes sense
// to extend.
diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h
index 75d0bfc90..55211dde5 100644
--- a/source/slang/slang-check-impl.h
+++ b/source/slang/slang-check-impl.h
@@ -1474,6 +1474,8 @@ public:
///
void ensureDeclBase(DeclBase* decl, DeclCheckState state, SemanticsContext* baseContext);
+ void ensureOuterGenericConstraints(Decl* decl, DeclCheckState state);
+
// Check if `lambdaStruct` can be coerced to `funcType`, if so returns the coerced
// expression in `outExpr`. The coercion is only valid if the lambda struct
// does not contain any captures.
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index dd7885281..8dbdaaaa6 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -1647,6 +1647,11 @@ DIAGNOSTIC(
requiredConstraintIsNotChecked,
"the constraint providing '$0' is optional and must be checked with an 'is' statement before "
"usage.")
+DIAGNOSTIC(
+ 30404,
+ Error,
+ invalidEqualityConstraintSupType,
+ "type '$0' is not a proper type to use in a generic equality constraint.")
// 305xx: initializer lists
DIAGNOSTIC(30500, Error, tooManyInitializers, "too many initializers (expected $0, got $1)")
@@ -1795,7 +1800,12 @@ DIAGNOSTIC(
Error,
unreferencedGenericParamInExtension,
"generic parameter '$0' is not referenced by extension target type '$1'.")
-
+DIAGNOSTIC(
+ 30856,
+ Warning,
+ genericParamInExtensionNotReferencedByTargetType,
+ "the extension is non-standard and may not work as intended because the generic parameter '$0' "
+ "is not referenced by extension target type '$1'.")
// 309xx: subscripts
DIAGNOSTIC(
30900,
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index a2fd944eb..6401b3d06 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -3649,7 +3649,7 @@ static void parseOptionalGenericConstraints(Parser* parser, ContainerDecl* decl)
// substitution needs to be filled during check
Type* paramType = nullptr;
- if (as<GenericTypeParamDeclBase>(decl))
+ if (as<GenericTypeParamDeclBase>(decl) || as<GlobalGenericParamDecl>(decl))
{
paramType = DeclRefType::create(parser->astBuilder, DeclRef<Decl>(decl));