summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-check-expr.cpp
diff options
context:
space:
mode:
authorJulius Ikkala <julius.ikkala@gmail.com>2025-06-28 05:39:24 +0300
committerGitHub <noreply@github.com>2025-06-28 02:39:24 +0000
commit7349dc5cff49cf22c82eb912813e47f30cd7a757 (patch)
tree4d7b3e14f119e7bb48623e52c890b461fd3d9701 /source/slang/slang-check-expr.cpp
parenta13dda4f214274a10d39f37c79622fc3e62da310 (diff)
Minimal optional constraints (#7422)
* Parse optional witness syntax * Allow failing optional constraint * Make `is` work with optional constraint * Allow using optional constraint in checked if statements * Fix tests * Make it work with structs * Fix MSVC build error * Disallow using `as` with optional constraints * Update test to match split is/as errors * Add tests * Fix uninitialized variables in tests * Add tests of incorrect uses & fix related bugs * Mention optional constraints in docs * format code * Fix type unification with NoneWitness * Fix formatting --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> Co-authored-by: Nathan V. Morrical <natemorrical@gmail.com>
Diffstat (limited to 'source/slang/slang-check-expr.cpp')
-rw-r--r--source/slang/slang-check-expr.cpp124
1 files changed, 113 insertions, 11 deletions
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index 4c6bf98d2..306687bd8 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -1023,6 +1023,100 @@ LookupResult SemanticsVisitor::filterLookupResultByVisibilityAndDiagnose(
return result;
}
+bool SemanticsVisitor::isWitnessUncheckedOptional(SubtypeWitness* witness)
+{
+ auto declaredWitness = as<DeclaredSubtypeWitness>(witness);
+ if (!declaredWitness)
+ return false;
+
+ auto decl = declaredWitness->getDeclRef().getDecl();
+ if (!decl || !decl->hasModifier<OptionalConstraintModifier>())
+ return false;
+
+ // Okay, we've found an optional subtype witness. This result needs
+ // to be removed if we're not inside a block that directly checks
+ // if (sub is sup)
+ auto sub = witness->getSub();
+ auto sup = witness->getSup();
+
+ for (auto outerStmtInfo = m_outerStmts; outerStmtInfo; outerStmtInfo = outerStmtInfo->next)
+ {
+ auto outerStmt = outerStmtInfo->stmt;
+ auto ifStmt = as<IfStmt>(outerStmt);
+
+ if (!ifStmt)
+ continue;
+
+ IsTypeExpr* isType = as<IsTypeExpr>(ifStmt->predicate);
+ if (!isType)
+ continue;
+ VarExpr* var = as<VarExpr>(isType->value);
+ if (!var)
+ continue;
+ TypeType* typeType = as<TypeType>(var->type);
+
+ // var->type works for `variable is Interface`, while
+ // typeType->getType() is for `T is Interface`.
+ auto type = typeType ? typeType->getType() : var->type.type;
+ if (type == sub && isType->typeExpr.type == sup)
+ {
+ return false;
+ }
+ }
+
+ // If we got this far, it's both an optional witness and there's no
+ // statement checking its validity.
+ return true;
+}
+
+LookupResult SemanticsVisitor::filterLookupResultByCheckedOptional(const LookupResult& lookupResult)
+{
+ LookupResult filteredResult;
+ for (auto item : lookupResult)
+ {
+ bool optionalConstraintsChecked = true;
+
+ for (auto bb = item.breadcrumbs; bb; bb = bb->next)
+ {
+ auto witness = as<SubtypeWitness>(bb->val);
+ if (!witness)
+ continue;
+
+ if (isWitnessUncheckedOptional(witness))
+ {
+ optionalConstraintsChecked = false;
+ break;
+ }
+ }
+
+ if (optionalConstraintsChecked)
+ AddToLookupResult(filteredResult, item);
+ }
+ return filteredResult;
+}
+
+LookupResult SemanticsVisitor::filterLookupResultByCheckedOptionalAndDiagnose(
+ const LookupResult& lookupResult,
+ SourceLoc loc,
+ bool& outDiagnosed)
+{
+ auto result = filterLookupResultByCheckedOptional(lookupResult);
+ if (lookupResult.isValid() && !result.isValid())
+ {
+ getSink()->diagnose(
+ loc,
+ Diagnostics::requiredConstraintIsNotChecked,
+ lookupResult.item.declRef);
+ outDiagnosed = true;
+
+ if (getShared()->isInLanguageServer())
+ {
+ return lookupResult;
+ }
+ }
+ return result;
+}
+
LookupResult SemanticsVisitor::resolveOverloadedLookup(LookupResult const& inResult)
{
// If the result isn't actually overloaded, it is fine as-is
@@ -4068,19 +4162,16 @@ Expr* SemanticsExprVisitor::visitIsTypeExpr(IsTypeExpr* expr)
expr->type = m_astBuilder->getBoolType();
expr->value = originalVal;
- // Check if the right-hand side type is an interface type
- if (isInterfaceType(expr->typeExpr.type))
- {
- getSink()->diagnose(expr, Diagnostics::isAsOperatorCannotUseInterfaceAsRHS);
- return expr;
- }
-
auto valueType = expr->value->type.type;
if (auto typeType = as<TypeType>(valueType))
valueType = typeType->getType();
// If value is a subtype of `type`, then this expr is always true.
- if (isSubtype(valueType, expr->typeExpr.type, IsSubTypeOptions::None))
+ auto witness = isSubtype(valueType, expr->typeExpr.type, IsSubTypeOptions::None);
+ auto declWitness = as<DeclaredSubtypeWitness>(witness);
+ bool optionalWitness = declWitness && declWitness->isOptional();
+
+ if (witness && !optionalWitness)
{
// Instead of returning a BoolLiteralExpr, we use a field to indicate this scenario,
// so that the language server can still see the original syntax tree.
@@ -4091,15 +4182,24 @@ Expr* SemanticsExprVisitor::visitIsTypeExpr(IsTypeExpr* expr)
return expr;
}
+ // Check if the right-hand side type is an interface type. For 'is'
+ // statements, that's only allowed if it's related to an optional
+ // constraint.
+ if (isInterfaceType(expr->typeExpr.type) && !optionalWitness)
+ {
+ getSink()->diagnose(expr, Diagnostics::isOperatorCannotUseInterfaceAsRHS);
+ return expr;
+ }
+
// Otherwise, if the target type is a subtype of value->type, we need to grab the
// subtype witness for runtime checks.
expr->value = maybeOpenExistential(originalVal);
- expr->witnessArg = tryGetSubtypeWitness(expr->typeExpr.type, valueType);
+ expr->witnessArg = witness ? witness : tryGetSubtypeWitness(expr->typeExpr.type, valueType);
if (expr->witnessArg)
{
// For now we can only support the scenario where `expr->value` is an interface type.
- if (!isInterfaceType(originalVal->type))
+ if (!optionalWitness && !isInterfaceType(originalVal->type))
{
getSink()->diagnose(expr, Diagnostics::isOperatorValueMustBeInterfaceType);
}
@@ -4117,7 +4217,7 @@ Expr* SemanticsExprVisitor::visitAsTypeExpr(AsTypeExpr* expr)
// Check if the right-hand side type is an interface type
if (isInterfaceType(typeExpr.type))
{
- getSink()->diagnose(expr, Diagnostics::isAsOperatorCannotUseInterfaceAsRHS);
+ getSink()->diagnose(expr, Diagnostics::asOperatorCannotUseInterfaceAsRHS);
expr->type = m_astBuilder->getErrorType();
return expr;
}
@@ -5162,6 +5262,8 @@ Expr* SemanticsVisitor::checkGeneralMemberLookupExpr(MemberExpr* expr, Type* bas
lookUpMember(m_astBuilder, this, expr->name, baseType, m_outerScope);
bool diagnosed = false;
lookupResult = filterLookupResultByVisibilityAndDiagnose(lookupResult, expr->loc, diagnosed);
+ lookupResult =
+ filterLookupResultByCheckedOptionalAndDiagnose(lookupResult, expr->loc, diagnosed);
if (!lookupResult.isValid())
{
return lookupMemberResultFailure(expr, baseType, diagnosed);