Skip to content

Commit 973d58d

Browse files
committed
[Diagnostics] Provide a tailored diagnostic for operator mismatch on missing conformance
If missing conformance is between two stdlib defined types which are used in operator invocation, let's produce a generic diagnostic about operator reference and a note about missing conformance.
1 parent 25d7a07 commit 973d58d

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,9 @@ bool MissingConformanceFailure::diagnoseAsError() {
573573
}
574574
}
575575

576+
if (diagnoseAsAmbiguousOperatorRef())
577+
return true;
578+
576579
Optional<unsigned> atParameterPos;
577580
// Sometimes fix is recorded by type-checking sub-expression
578581
// during normal diagnostics, in such case call expression
@@ -614,6 +617,52 @@ bool MissingConformanceFailure::diagnoseAsError() {
614617
return RequirementFailure::diagnoseAsError();
615618
}
616619

620+
bool MissingConformanceFailure::diagnoseAsAmbiguousOperatorRef() {
621+
auto *anchor = getRawAnchor();
622+
auto *ODRE = dyn_cast<OverloadedDeclRefExpr>(anchor);
623+
if (!ODRE)
624+
return false;
625+
626+
auto isStdlibType = [](Type type) {
627+
if (auto *NTD = type->getAnyNominal()) {
628+
auto *DC = NTD->getDeclContext();
629+
return DC->isModuleScopeContext() &&
630+
DC->getParentModule()->isStdlibModule();
631+
}
632+
633+
return false;
634+
};
635+
636+
auto name = ODRE->getDecls().front()->getBaseName();
637+
if (!(name.isOperator() && isStdlibType(getLHS()) && isStdlibType(getRHS())))
638+
return false;
639+
640+
// If this is an operator reference and both types are from stdlib,
641+
// let's produce a generic diagnostic about invocation and a note
642+
// about missing conformance just in case.
643+
auto operatorID = name.getIdentifier();
644+
645+
auto *applyExpr = cast<ApplyExpr>(findParentExpr(anchor));
646+
if (auto *binaryOp = dyn_cast<BinaryExpr>(applyExpr)) {
647+
auto lhsType = getType(binaryOp->getArg()->getElement(0));
648+
auto rhsType = getType(binaryOp->getArg()->getElement(1));
649+
650+
if (lhsType->isEqual(rhsType)) {
651+
emitDiagnostic(anchor->getLoc(), diag::cannot_apply_binop_to_same_args,
652+
operatorID.str(), lhsType);
653+
} else {
654+
emitDiagnostic(anchor->getLoc(), diag::cannot_apply_binop_to_args,
655+
operatorID.str(), lhsType, rhsType);
656+
}
657+
} else {
658+
emitDiagnostic(anchor->getLoc(), diag::cannot_apply_unop_to_arg,
659+
operatorID.str(), getType(applyExpr->getArg()));
660+
}
661+
662+
diagnoseAsNote();
663+
return true;
664+
}
665+
617666
Optional<Diag<Type, Type>> GenericArgumentsMismatchFailure::getDiagnosticFor(
618667
ContextualTypePurpose context) {
619668
switch (context) {

lib/Sema/CSDiagnostics.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,14 @@ class MissingConformanceFailure final : public RequirementFailure {
327327
bool diagnoseAsError() override;
328328

329329
protected:
330+
/// Check whether this requirement is associated with one of the
331+
/// operator overloads, in cases like that sometimes it makes more
332+
/// sense to produce a generic diagnostic about operator reference
333+
/// instead of conformance, because it could be something like
334+
/// `true + true`, and it doesn't make much sense to suggest to
335+
/// add a conformance from one library type to another.
336+
bool diagnoseAsAmbiguousOperatorRef();
337+
330338
DiagOnDecl getDiagnosticOnDecl() const override {
331339
return diag::type_does_not_conform_decl_owner;
332340
}

0 commit comments

Comments
 (0)