summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-check-expr.cpp
diff options
context:
space:
mode:
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);