diff options
Diffstat (limited to 'source/slang/slang-check-expr.cpp')
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 124 |
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); |
