Skip to content

Commit b898eaf

Browse files
committed
[Diagnostics] Tailored diagnostics for reference equality operator mismatches
1 parent 973d58d commit b898eaf

File tree

6 files changed

+116
-93
lines changed

6 files changed

+116
-93
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -462,12 +462,6 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
462462
/// exact expression kind).
463463
bool diagnoseGeneralConversionFailure(Constraint *constraint);
464464

465-
/// Produce a diagnostic for binary comparisons of the nil literal
466-
/// to other values.
467-
bool diagnoseNilLiteralComparison(Expr *lhsExpr, Expr *rhsExpr,
468-
CalleeCandidateInfo &calleeInfo,
469-
SourceLoc applyLoc);
470-
471465
bool diagnoseMemberFailures(
472466
Expr *E, Expr *baseEpxr, ConstraintKind lookupKind, DeclName memberName,
473467
FunctionRefKind funcRefKind, ConstraintLocator *locator,
@@ -3004,60 +2998,6 @@ static bool isNameOfStandardComparisonOperator(StringRef opName) {
30042998
opName == "<=" || opName == ">=";
30052999
}
30063000

3007-
bool FailureDiagnosis::diagnoseNilLiteralComparison(
3008-
Expr *lhsExpr, Expr *rhsExpr, CalleeCandidateInfo &calleeInfo,
3009-
SourceLoc applyLoc) {
3010-
3011-
auto overloadName = calleeInfo.declName;
3012-
3013-
// Only diagnose for comparison operators.
3014-
if (!isNameOfStandardComparisonOperator(overloadName))
3015-
return false;
3016-
3017-
Expr *otherExpr = lhsExpr;
3018-
Expr *nilExpr = rhsExpr;
3019-
3020-
// Swap if we picked the wrong side as the nil literal.
3021-
if (!isa<NilLiteralExpr>(nilExpr->getValueProvidingExpr()))
3022-
std::swap(otherExpr, nilExpr);
3023-
3024-
// Bail if neither side is a nil literal.
3025-
if (!isa<NilLiteralExpr>(nilExpr->getValueProvidingExpr()))
3026-
return false;
3027-
3028-
// Bail if both sides are a nil literal.
3029-
if (isa<NilLiteralExpr>(otherExpr->getValueProvidingExpr()))
3030-
return false;
3031-
3032-
auto otherType = CS.getType(otherExpr)->getRValueType();
3033-
3034-
// Bail if we were unable to determine the other type.
3035-
if (isUnresolvedOrTypeVarType(otherType))
3036-
return false;
3037-
3038-
// Regardless of whether the type has reference or value semantics,
3039-
// comparison with nil is illegal, albeit for different reasons spelled
3040-
// out by the diagnosis.
3041-
if (otherType->getOptionalObjectType() &&
3042-
(overloadName == "!==" || overloadName == "===")) {
3043-
auto revisedName = overloadName;
3044-
revisedName.pop_back();
3045-
3046-
// If we made it here, then we're trying to perform a comparison with
3047-
// reference semantics rather than value semantics. The fixit will
3048-
// lop off the extra '=' in the operator.
3049-
diagnose(applyLoc,
3050-
diag::value_type_comparison_with_nil_illegal_did_you_mean,
3051-
otherType)
3052-
.fixItReplace(applyLoc, revisedName);
3053-
} else {
3054-
diagnose(applyLoc, diag::value_type_comparison_with_nil_illegal, otherType)
3055-
.highlight(otherExpr->getSourceRange());
3056-
}
3057-
3058-
return true;
3059-
}
3060-
30613001
static bool diagnoseClosureExplicitParameterMismatch(
30623002
ConstraintSystem &CS, SourceLoc loc,
30633003
ArrayRef<AnyFunctionType::Param> params,
@@ -3709,11 +3649,6 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
37093649
auto lhsType = CS.getType(lhsExpr)->getRValueType();
37103650
auto rhsType = CS.getType(rhsExpr)->getRValueType();
37113651

3712-
// Diagnose any comparisons with the nil literal.
3713-
if (diagnoseNilLiteralComparison(lhsExpr, rhsExpr, calleeInfo,
3714-
callExpr->getLoc()))
3715-
return true;
3716-
37173652
// TODO(diagnostics): There are still cases not yet handled by new
37183653
// diagnostics framework e.g.
37193654
//
@@ -3732,18 +3667,6 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
37323667
return failure.diagnosePatternMatchingMismatch();
37333668
}
37343669

3735-
// Diagnose attempts to compare reference equality of certain types.
3736-
if (overloadName == "===" || overloadName == "!==") {
3737-
// Functions.
3738-
if (lhsType->is<AnyFunctionType>() || rhsType->is<AnyFunctionType>()) {
3739-
diagnose(callExpr->getLoc(), diag::cannot_reference_compare_types,
3740-
overloadName, lhsType, rhsType)
3741-
.highlight(lhsExpr->getSourceRange())
3742-
.highlight(rhsExpr->getSourceRange());
3743-
return true;
3744-
}
3745-
}
3746-
37473670
if (isContextualConversionFailure(argTuple))
37483671
return false;
37493672

lib/Sema/CSDiagnostics.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4526,6 +4526,9 @@ bool ArgumentMismatchFailure::diagnoseAsError() {
45264526
if (diagnosePatternMatchingMismatch())
45274527
return true;
45284528

4529+
if (diagnoseUseOfReferenceEqualityOperator())
4530+
return true;
4531+
45294532
auto argType = getFromType();
45304533
auto paramType = getToType();
45314534

@@ -4568,6 +4571,71 @@ bool ArgumentMismatchFailure::diagnoseAsNote() {
45684571
return false;
45694572
}
45704573

4574+
bool ArgumentMismatchFailure::diagnoseUseOfReferenceEqualityOperator() const {
4575+
auto *locator = getLocator();
4576+
4577+
if (!isArgumentOfReferenceEqualityOperator(locator))
4578+
return false;
4579+
4580+
auto *binaryOp = cast<BinaryExpr>(getRawAnchor());
4581+
auto *lhs = binaryOp->getArg()->getElement(0);
4582+
auto *rhs = binaryOp->getArg()->getElement(1);
4583+
4584+
auto name = *getOperatorName(binaryOp->getFn());
4585+
4586+
auto lhsType = getType(lhs)->getRValueType();
4587+
auto rhsType = getType(rhs)->getRValueType();
4588+
4589+
// If both arguments where incorrect e.g. both are function types,
4590+
// let's avoid producing a diagnostic second time, because first
4591+
// one would cover both arguments.
4592+
if (getAnchor() == rhs && rhsType->is<FunctionType>()) {
4593+
auto &cs = getConstraintSystem();
4594+
if (cs.hasFixFor(cs.getConstraintLocator(
4595+
binaryOp, {ConstraintLocator::ApplyArgument,
4596+
LocatorPathElt::ApplyArgToParam(0, 0)})))
4597+
return true;
4598+
}
4599+
4600+
// Regardless of whether the type has reference or value semantics,
4601+
// comparison with nil is illegal, albeit for different reasons spelled
4602+
// out by the diagnosis.
4603+
if (isa<NilLiteralExpr>(lhs) || isa<NilLiteralExpr>(rhs)) {
4604+
std::string revisedName = name.str();
4605+
revisedName.pop_back();
4606+
4607+
auto loc = binaryOp->getLoc();
4608+
auto nonNilType = isa<NilLiteralExpr>(lhs) ? rhsType : lhsType;
4609+
auto nonNilExpr = isa<NilLiteralExpr>(lhs) ? rhs : lhs;
4610+
4611+
// If we made it here, then we're trying to perform a comparison with
4612+
// reference semantics rather than value semantics. The fixit will
4613+
// lop off the extra '=' in the operator.
4614+
if (nonNilType->getOptionalObjectType()) {
4615+
emitDiagnostic(loc,
4616+
diag::value_type_comparison_with_nil_illegal_did_you_mean,
4617+
nonNilType)
4618+
.fixItReplace(loc, revisedName);
4619+
} else {
4620+
emitDiagnostic(loc, diag::value_type_comparison_with_nil_illegal,
4621+
nonNilType)
4622+
.highlight(nonNilExpr->getSourceRange());
4623+
}
4624+
4625+
return true;
4626+
}
4627+
4628+
if (lhsType->is<FunctionType>() || rhsType->is<FunctionType>()) {
4629+
emitDiagnostic(binaryOp->getLoc(), diag::cannot_reference_compare_types,
4630+
name.str(), lhsType, rhsType)
4631+
.highlight(lhs->getSourceRange())
4632+
.highlight(rhs->getSourceRange());
4633+
return true;
4634+
}
4635+
4636+
return false;
4637+
}
4638+
45714639
bool ArgumentMismatchFailure::diagnosePatternMatchingMismatch() const {
45724640
if (!isArgumentOfPatternMatchingOperator(getLocator()))
45734641
return false;

lib/Sema/CSDiagnostics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,10 @@ class ArgumentMismatchFailure : public ContextualFailure {
16131613
/// Tailored diagnostic for pattern matching with `~=` operator.
16141614
bool diagnosePatternMatchingMismatch() const;
16151615

1616+
/// Tailored diagnostics for argument mismatches associated with
1617+
/// reference equality operators `===` and `!==`.
1618+
bool diagnoseUseOfReferenceEqualityOperator() const;
1619+
16161620
protected:
16171621
SourceLoc getLoc() const { return getAnchor()->getLoc(); }
16181622

lib/Sema/CSSimplify.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,6 +2433,15 @@ bool ConstraintSystem::repairFailures(
24332433
repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator))
24342434
break;
24352435

2436+
// If this is an argument to `===` or `!==` there are tailored
2437+
// diagnostics available for it as part of argument-to-parameter
2438+
// conversion fix, so let's not try any restrictions or other fixes.
2439+
if (isArgumentOfReferenceEqualityOperator(loc)) {
2440+
conversionsOrFixes.push_back(
2441+
AllowArgumentMismatch::create(*this, lhs, rhs, loc));
2442+
break;
2443+
}
2444+
24362445
// Argument is a r-value but parameter expects an l-value e.g.
24372446
//
24382447
// func foo(_ x: inout Int) {}

lib/Sema/ConstraintSystem.cpp

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,37 +2960,48 @@ bool constraints::isKnownKeyPathDecl(ASTContext &ctx, ValueDecl *decl) {
29602960
decl == ctx.getPartialKeyPathDecl() || decl == ctx.getAnyKeyPathDecl();
29612961
}
29622962

2963-
bool constraints::isPatternMatchingOperator(Expr *expr) {
2963+
static bool isOperator(Expr *expr, StringRef expectedName) {
2964+
auto name = getOperatorName(expr);
2965+
return name ? name->is(expectedName) : false;
2966+
}
2967+
2968+
Optional<Identifier> constraints::getOperatorName(Expr *expr) {
29642969
ValueDecl *choice = nullptr;
29652970
if (auto *ODRE = dyn_cast_or_null<OverloadedDeclRefExpr>(expr)) {
29662971
choice = ODRE->getDecls().front();
29672972
} else if (auto *DRE = dyn_cast_or_null<DeclRefExpr>(expr)) {
29682973
choice = DRE->getDecl();
29692974
} else {
2970-
return false;
2975+
return None;
29712976
}
29722977

2973-
if (auto *FD = dyn_cast_or_null<AbstractFunctionDecl>(choice)) {
2974-
auto name = FD->getBaseName().userFacingName();
2975-
return name == "~=";
2976-
}
2978+
if (auto *FD = dyn_cast_or_null<AbstractFunctionDecl>(choice))
2979+
return FD->getBaseName().getIdentifier();
29772980

2978-
return false;
2981+
return None;
2982+
}
2983+
2984+
bool constraints::isPatternMatchingOperator(Expr *expr) {
2985+
return isOperator(expr, "~=");
29792986
}
29802987

29812988
bool constraints::isArgumentOfPatternMatchingOperator(
29822989
ConstraintLocator *locator) {
2983-
Expr *anchor = nullptr;
2984-
if (locator->findLast<LocatorPathElt::ApplyArgToParam>()) {
2985-
anchor = locator->getAnchor();
2986-
} else {
2990+
auto *binaryOp = dyn_cast_or_null<BinaryExpr>(locator->getAnchor());
2991+
if (!(binaryOp && binaryOp->isImplicit()))
29872992
return false;
2988-
}
2993+
return isPatternMatchingOperator(binaryOp->getFn());
2994+
}
29892995

2990-
auto *binaryOp = dyn_cast_or_null<BinaryExpr>(anchor);
2991-
if (!(binaryOp && binaryOp->isImplicit()))
2996+
bool constraints::isArgumentOfReferenceEqualityOperator(
2997+
ConstraintLocator *locator) {
2998+
if (!locator->findLast<LocatorPathElt::ApplyArgToParam>())
29922999
return false;
29933000

2994-
auto *fnExpr = binaryOp->getFn()->getSemanticsProvidingExpr();
2995-
return isPatternMatchingOperator(fnExpr);
3001+
if (auto *binaryOp = dyn_cast_or_null<BinaryExpr>(locator->getAnchor())) {
3002+
auto *fnExpr = binaryOp->getFn();
3003+
return isOperator(fnExpr, "===") || isOperator(fnExpr, "!==");
3004+
}
3005+
3006+
return false;
29963007
}

lib/Sema/ConstraintSystem.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3991,10 +3991,18 @@ Expr *getArgumentExpr(Expr *expr, unsigned index);
39913991
/// associated with implicit `~=` (pattern-matching) operator
39923992
bool isArgumentOfPatternMatchingOperator(ConstraintLocator *locator);
39933993

3994+
/// Determine whether given locator points to one of the arguments
3995+
/// associated with `===` and `!==` operators.
3996+
bool isArgumentOfReferenceEqualityOperator(ConstraintLocator *locator);
3997+
39943998
/// Determine whether given expression is a reference to a
39953999
/// pattern-matching operator `~=`
39964000
bool isPatternMatchingOperator(Expr *expr);
39974001

4002+
/// If given expression references operator overlaod(s)
4003+
/// extract and produce name of the operator.
4004+
Optional<Identifier> getOperatorName(Expr *expr);
4005+
39984006
// Check whether argument of the call at given position refers to
39994007
// parameter marked as `@autoclosure`. This function is used to
40004008
// maintain source compatibility with Swift versions < 5,

0 commit comments

Comments
 (0)