Skip to content

[Diagnostics] NFC: Move logic common to all requirement failures into… #18780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1529,8 +1529,10 @@ ERROR(type_does_not_conform_in_decl_ref,none,
ERROR(type_does_not_conform_decl_owner,none,
"%0 %1 requires that %2 conform to %3",
(DescriptiveDeclKind, DeclName, Type, Type))
NOTE(where_type_does_not_conform_type,none,
NOTE(where_requirement_failure_one_subst,none,
"where %0 = %1", (Type, Type))
NOTE(where_requirement_failure_both_subst,none,
"where %0 = %1, %2 = %3", (Type, Type, Type, Type))
NOTE(requirement_implied_by_conditional_conformance,none,
"requirement from conditional conformance of %0 to %1", (Type, Type))
NOTE(candidate_types_equal_requirement,none,
Expand Down
124 changes: 68 additions & 56 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/AST/GenericSignature.h"
#include "swift/AST/Types.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallString.h"

using namespace swift;
using namespace constraints;
Expand Down Expand Up @@ -122,27 +123,59 @@ const DeclContext *RequirementFailure::getRequirementDC() const {
return AffectedDecl->getAsGenericContext();
}

bool RequirementFailure::diagnose() {
if (!canDiagnoseFailure())
return false;

auto *anchor = getAnchor();
const auto *reqDC = getRequirementDC();
auto *genericCtx = AffectedDecl->getAsGenericContext();

if (reqDC != genericCtx) {
auto *NTD = reqDC->getAsNominalTypeOrNominalTypeExtensionContext();
emitDiagnostic(anchor->getLoc(), getDiagnosticInRereference(),
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(), NTD->getDeclaredType(),
getLHS(), getRHS());
} else {
emitDiagnostic(anchor->getLoc(), getDiagnosticOnDecl(),
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(), getLHS(), getRHS());
}

emitRequirementNote(reqDC->getAsDeclOrDeclExtensionContext());
return true;
}

void RequirementFailure::emitRequirementNote(const Decl *anchor) const {
auto &req = getRequirement();

if (getRHS()->isEqual(req.getSecondType())) {
emitDiagnostic(anchor, diag::where_requirement_failure_one_subst,
req.getFirstType(), getLHS());
return;
}

if (getLHS()->isEqual(req.getFirstType())) {
emitDiagnostic(anchor, diag::where_requirement_failure_one_subst,
req.getSecondType(), getRHS());
return;
}

emitDiagnostic(anchor, diag::where_requirement_failure_both_subst,
req.getFirstType(), getLHS(), req.getSecondType(), getRHS());
}

bool MissingConformanceFailure::diagnose() {
if (!canDiagnoseFailure())
return false;

auto *anchor = getAnchor();
auto ownerType = getOwnerType();
auto type = getNonConformingType();
auto protocolType = getProtocolType();

// Find `ApplyExpr` based on a function expression attached to it.
auto findApplyExpr = [](Expr *parent, Expr *fnExpr) -> ApplyExpr * {
ApplyExpr *applyExpr = nullptr;
parent->forEachChildExpr([&applyExpr, &fnExpr](Expr *subExpr) -> Expr * {
auto *AE = dyn_cast<ApplyExpr>(subExpr);
if (!AE || AE->getFn() != fnExpr)
return subExpr;

applyExpr = AE;
return nullptr;
});
return applyExpr;
};
auto nonConformingType = getLHS();
auto protocolType = getRHS();

auto getArgumentAt = [](ApplyExpr *AE, unsigned index) -> Expr * {
auto getArgumentAt = [](const ApplyExpr *AE, unsigned index) -> Expr * {
assert(AE);

auto *arg = AE->getArg();
Expand All @@ -156,66 +189,45 @@ bool MissingConformanceFailure::diagnose() {
return arg;
};

auto *applyExpr = findApplyExpr(getParentExpr(), anchor);

Optional<unsigned> atParameterPos;
// Sometimes fix is recorded by type-checking sub-expression
// during normal diagnostics, in such case call expression
// is unavailable.
if (applyExpr) {
// If this is a static, initializer or operator call,
// let's not try to diagnose it here, but refer to expression
// diagnostics.
if (isa<PrefixUnaryExpr>(applyExpr) || isa<PostfixUnaryExpr>(applyExpr) ||
isa<BinaryExpr>(applyExpr) || isa<TypeExpr>(anchor))
return false;

if (Apply) {
if (auto *fnType = ownerType->getAs<AnyFunctionType>()) {
auto parameters = fnType->getParams();
for (auto index : indices(parameters)) {
if (parameters[index].getType()->isEqual(type)) {
if (parameters[index].getType()->isEqual(nonConformingType)) {
atParameterPos = index;
break;
}
}
}
}

if (type->isExistentialType()) {
if (nonConformingType->isExistentialType()) {
auto diagnostic = diag::protocol_does_not_conform_objc;
if (type->isObjCExistentialType())
if (nonConformingType->isObjCExistentialType())
diagnostic = diag::protocol_does_not_conform_static;

emitDiagnostic(anchor->getLoc(), diagnostic, type, protocolType);
} else if (atParameterPos) {
emitDiagnostic(anchor->getLoc(), diagnostic, nonConformingType,
protocolType);
return true;
}

if (atParameterPos) {
// Requirement comes from one of the parameter types,
// let's try to point diagnostic to the argument expression.
auto *argExpr = getArgumentAt(applyExpr, *atParameterPos);
auto *argExpr = getArgumentAt(Apply, *atParameterPos);
emitDiagnostic(argExpr->getLoc(),
diag::cannot_convert_argument_value_protocol, type,
protocolType);
} else {
const auto &req = getRequirement();
auto *genericCtx = AffectedDecl->getAsGenericContext();
const auto *reqDC = getRequirementDC();

if (reqDC != genericCtx) {
auto *NTD = reqDC->getAsNominalTypeOrNominalTypeExtensionContext();
emitDiagnostic(anchor->getLoc(), diag::type_does_not_conform_in_decl_ref,
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(), NTD->getDeclaredType(), type,
protocolType);
} else {
emitDiagnostic(anchor->getLoc(), diag::type_does_not_conform_decl_owner,
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(), type, protocolType);
}

emitDiagnostic(reqDC->getAsDeclOrDeclExtensionContext(),
diag::where_type_does_not_conform_type, req.getFirstType(),
type);
diag::cannot_convert_argument_value_protocol,
nonConformingType, protocolType);
return true;
}
return true;

// If none of the special cases could be diagnosed,
// let's fallback to the most general diagnostic.
return RequirementFailure::diagnose();
}

bool LabelingFailure::diagnose() {
Expand Down
57 changes: 54 additions & 3 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "Constraint.h"
#include "ConstraintSystem.h"
#include "OverloadChoice.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Types.h"
#include "llvm/ADT/ArrayRef.h"
Expand Down Expand Up @@ -110,15 +111,30 @@ class FailureDiagnostic {
/// failures, provides common information like failed requirement,
/// declaration where such requirement comes from, etc.
class RequirementFailure : public FailureDiagnostic {
protected:
using PathEltKind = ConstraintLocator::PathElementKind;
using DiagOnDecl = Diag<DescriptiveDeclKind, DeclName, Type, Type>;
using DiagInReference = Diag<DescriptiveDeclKind, DeclName, Type, Type, Type>;

protected:
const ValueDecl *AffectedDecl;
/// If possible, find application expression associated
/// with current generic requirement failure, that helps
/// to diagnose failures related to arguments.
const ApplyExpr *Apply = nullptr;

public:
RequirementFailure(Expr *expr, const Solution &solution,
ConstraintLocator *locator)
: FailureDiagnostic(expr, solution, locator), AffectedDecl(getDeclRef()) {
auto *anchor = getAnchor();
expr->forEachChildExpr([&](Expr *subExpr) -> Expr * {
auto *AE = dyn_cast<ApplyExpr>(subExpr);
if (!AE || AE->getFn() != anchor)
return subExpr;

Apply = AE;
return nullptr;
});
}

unsigned getRequirementIndex() const {
Expand All @@ -136,14 +152,40 @@ class RequirementFailure : public FailureDiagnostic {
/// Generic requirement associated with the failure.
const Requirement &getRequirement() const;

virtual Type getLHS() const = 0;
virtual Type getRHS() const = 0;

bool diagnose() override;

protected:
/// Retrieve declaration contextual where current
/// requirement has been introduced.
const DeclContext *getRequirementDC() const;

virtual DiagOnDecl getDiagnosticOnDecl() const = 0;
virtual DiagInReference getDiagnosticInRereference() const = 0;

/// Determine whether it would be possible to diagnose
/// current requirement failure.
bool canDiagnoseFailure() const {
// For static/initializer calls there is going to be
// a separate fix, attached to the argument, which is
// much easier to diagnose.
// For operator calls we can't currently produce a good
// diagnostic, so instead let's refer to expression diagnostics.
return !(Apply && (isOperator(Apply) || isa<TypeExpr>(getAnchor())));
}

static bool isOperator(const ApplyExpr *apply) {
return isa<PrefixUnaryExpr>(apply) || isa<PostfixUnaryExpr>(apply) ||
isa<BinaryExpr>(apply);
}

private:
/// Retrieve declaration associated with failing generic requirement.
ValueDecl *getDeclRef() const;

void emitRequirementNote(const Decl *anchor) const;
};

/// Diagnostics for failed conformance checks originating from
Expand All @@ -169,10 +211,19 @@ class MissingConformanceFailure final : public RequirementFailure {
private:
/// The type which was expected, by one of the generic requirements,
/// to conform to associated protocol.
Type getNonConformingType() const { return NonConformingType; }
Type getLHS() const override { return NonConformingType; }

/// The protocol generic requirement expected associated type to conform to.
Type getProtocolType() const { return Protocol->getDeclaredType(); }
Type getRHS() const override { return Protocol->getDeclaredType(); }

protected:
DiagOnDecl getDiagnosticOnDecl() const override {
return diag::type_does_not_conform_decl_owner;
}

DiagInReference getDiagnosticInRereference() const override {
return diag::type_does_not_conform_in_decl_ref;
}
};

/// Diagnose errors associated with missing, extraneous
Expand Down