diff options
| author | Yong He <yonghe@outlook.com> | 2025-07-10 21:14:16 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-11 04:14:16 +0000 |
| commit | 90c34e3db4fdc7be79c62bd91905a2a84bbd673e (patch) | |
| tree | a8ccf3c2dbfe5f1faa646bf91e41d9a12a66c804 /source/slang | |
| parent | 7764b83d24d341334ca7c1693cae2472be8f8d99 (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.cpp | 98 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 12 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 2 |
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)); |
