Skip to content

[ConstraintSystem] Overhaul ambiguity diagnostics #31713

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 22 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6b52016
[ConstraintSystem] Rank solutions based on overload choices only afte…
xedin May 4, 2020
a888518
[ConstraintSystem] Encapsulate a way to diagnose ambiguity for aggreg…
xedin May 4, 2020
7d3f5e3
[ConstraintSystem] Overhaul ambiguity diagnostics
xedin May 11, 2020
cf3f52c
[ConstraintSystem] NFC: Leave notes on the algorithm used by `diagnos…
xedin May 11, 2020
d9594c7
[TypeChecker] NFC: Adjust tests improved by new approach for ambiguit…
xedin May 14, 2020
7641d9c
[CSFix] Make it possible to diagnose r-value -> l-value fix for ambig…
xedin May 20, 2020
ac2305e
[ConstraintSystem] Increase impact of fixes for some conditional requ…
xedin May 21, 2020
9067ed2
[ConstraintSystem] Specialize diagnostic for `~=` to talk about expre…
xedin May 21, 2020
b9aa70b
[ConstraintSystem] NFC/Debug: Log solutions before attempting to diag…
xedin Jun 8, 2020
7bbf9fe
[Diagnostics] If aggregate fix consists of identical fixes diagnose i…
xedin Jun 8, 2020
b0070f5
[Diagnostics] Check whether all contextual mismatches has the same ty…
xedin Jun 9, 2020
13b4fcc
[Diagnostics] Diagnose ambiguity related to contextual type specifically
xedin Jun 10, 2020
e2e57ad
[ConstraintSystem] Turn 'omit ' into a contextual mismatch it is
xedin Jun 10, 2020
b407f7c
[ConstraintSystem] Avoid recording duplicate fixes for contextual typ…
xedin Jun 10, 2020
d23b938
[Diagnostics] Implement `IgnoreAssignmentDestinationType::diagnoseFor…
xedin Jun 11, 2020
3ebf633
[ConstraintSystem] Increase impact of conformance failure associated …
xedin Jun 11, 2020
0af9a28
[Diagnostics] Allow property wrapper fixes to be diagnosed as ambiguity
xedin Jun 11, 2020
82fcee7
[Diagnostics] NFC: Adjust diagnostic test-cases improved by new ambig…
xedin Jun 12, 2020
f854cc6
[ConstraintSystem] Attach candidate notes either to decl (if it has v…
xedin Jun 12, 2020
2c0b8ca
[Diagnostics] Add a tailored diagnostic for function mismatch in argu…
xedin Jun 12, 2020
c0840fd
[TypeChecker] NFC: Restore test-cases removed by incorrect merge
xedin Jun 12, 2020
73667cd
[Diagnostics] Use `Id_MatchOperator` to check whether given operator …
xedin Jun 15, 2020
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: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ ERROR(no_candidates_match_result_type,none,
"no '%0' candidates produce the expected contextual result type %1",
(StringRef, Type))

ERROR(no_candidates_match_argument_type,none,
"no '%0' candidates produce the expected type %1 for parameter #%2",
(StringRef, Type, unsigned))

ERROR(cannot_infer_closure_parameter_type,none,
"unable to infer type of a closure parameter %0 in the current context",
(StringRef))
Expand Down
124 changes: 122 additions & 2 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,33 @@ bool ContextualMismatch::diagnose(const Solution &solution, bool asNote) const {
return failure.diagnose(asNote);
}

bool ContextualMismatch::diagnoseForAmbiguity(
CommonFixesArray commonFixes) const {
auto getTypes =
[&](const std::pair<const Solution *, const ConstraintFix *> &entry)
-> std::pair<Type, Type> {
auto &solution = *entry.first;
auto *fix = static_cast<const ContextualMismatch *>(entry.second);

return {solution.simplifyType(fix->getFromType()),
solution.simplifyType(fix->getToType())};
};

auto etalonTypes = getTypes(commonFixes.front());
if (llvm::all_of(
commonFixes,
[&](const std::pair<const Solution *, const ConstraintFix *> &entry) {
auto types = getTypes(entry);
return etalonTypes.first->isEqual(types.first) &&
etalonTypes.second->isEqual(types.second);
})) {
const auto &primary = commonFixes.front();
return primary.second->diagnose(*primary.first, /*asNote=*/false);
}

return false;
}

ContextualMismatch *ContextualMismatch::create(ConstraintSystem &cs, Type lhs,
Type rhs,
ConstraintLocator *locator) {
Expand Down Expand Up @@ -381,6 +408,57 @@ bool AllowFunctionTypeMismatch::diagnose(const Solution &solution,
return coalesceAndDiagnose(solution, {}, asNote);
}

bool AllowFunctionTypeMismatch::diagnoseForAmbiguity(
CommonFixesArray commonFixes) const {
if (ContextualMismatch::diagnoseForAmbiguity(commonFixes))
return true;

auto *locator = getLocator();
// If this is a mismatch between two function types at argument
// position, there is a tailored diagnostic for that.
if (auto argConv =
locator->getLastElementAs<LocatorPathElt::ApplyArgToParam>()) {
auto &cs = getConstraintSystem();
auto &DE = cs.getASTContext().Diags;
auto &solution = *commonFixes[0].first;

auto info = getStructuralTypeContext(solution, locator);
if (!info)
return false;

auto *argLoc = cs.getConstraintLocator(simplifyLocatorToAnchor(locator));

auto overload = solution.getOverloadChoiceIfAvailable(
solution.getCalleeLocator(argLoc));
if (!overload)
return false;

auto name = overload->choice.getName().getBaseName();
DE.diagnose(getLoc(getAnchor()), diag::no_candidates_match_argument_type,
name.userFacingName(), std::get<2>(*info),
argConv->getParamIdx());

for (auto &entry : commonFixes) {
auto &solution = *entry.first;
auto overload = solution.getOverloadChoiceIfAvailable(
solution.getCalleeLocator(argLoc));

if (!(overload && overload->choice.isDecl()))
continue;

auto *decl = overload->choice.getDecl();
if (decl->getLoc().isValid()) {
DE.diagnose(decl, diag::found_candidate_type,
solution.simplifyType(overload->openedType));
}
}

return true;
}

return false;
}

AllowFunctionTypeMismatch *
AllowFunctionTypeMismatch::create(ConstraintSystem &cs, Type lhs, Type rhs,
ConstraintLocator *locator, unsigned index) {
Expand Down Expand Up @@ -867,14 +945,19 @@ RemoveAddressOf *RemoveAddressOf::create(ConstraintSystem &cs, Type lhs, Type rh
return new (cs.getAllocator()) RemoveAddressOf(cs, lhs, rhs, locator);
}

RemoveReturn::RemoveReturn(ConstraintSystem &cs, Type resultTy,
ConstraintLocator *locator)
: ContextualMismatch(cs, FixKind::RemoveReturn, resultTy,
cs.getASTContext().TheEmptyTupleType, locator) {}

bool RemoveReturn::diagnose(const Solution &solution, bool asNote) const {
ExtraneousReturnFailure failure(solution, getLocator());
return failure.diagnose(asNote);
}

RemoveReturn *RemoveReturn::create(ConstraintSystem &cs,
RemoveReturn *RemoveReturn::create(ConstraintSystem &cs, Type resultTy,
ConstraintLocator *locator) {
return new (cs.getAllocator()) RemoveReturn(cs, locator);
return new (cs.getAllocator()) RemoveReturn(cs, resultTy, locator);
}

bool CollectionElementContextualMismatch::diagnose(const Solution &solution,
Expand Down Expand Up @@ -1079,6 +1162,43 @@ bool IgnoreAssignmentDestinationType::diagnose(const Solution &solution,
return failure.diagnose(asNote);
}

bool IgnoreAssignmentDestinationType::diagnoseForAmbiguity(
CommonFixesArray commonFixes) const {
auto &cs = getConstraintSystem();

// If all of the types are the same let's try to diagnose
// this as if there is no ambiguity.
if (ContextualMismatch::diagnoseForAmbiguity(commonFixes))
return true;

auto *commonLocator = getLocator();
auto *assignment = castToExpr<AssignExpr>(commonLocator->getAnchor());

auto &solution = *commonFixes.front().first;
auto *calleeLocator = solution.getCalleeLocator(
solution.getConstraintLocator(assignment->getSrc()));
auto overload = solution.getOverloadChoiceIfAvailable(calleeLocator);
if (!overload)
return false;

auto memberName = overload->choice.getName().getBaseName();
auto destType = solution.getType(assignment->getDest());

auto &DE = cs.getASTContext().Diags;
// TODO(diagnostics): It might be good to add a tailored diagnostic
// for cases like this instead of using "contextual" one.
DE.diagnose(assignment->getSrc()->getLoc(),
diag::no_candidates_match_result_type,
memberName.userFacingName(),
solution.simplifyType(destType)->getRValueType());

for (auto &entry : commonFixes) {
entry.second->diagnose(*entry.first, /*asNote=*/true);
}

return true;
}

IgnoreAssignmentDestinationType *
IgnoreAssignmentDestinationType::create(ConstraintSystem &cs, Type sourceTy,
Type destTy,
Expand Down
30 changes: 22 additions & 8 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ class TreatRValueAsLValue final : public ConstraintFix {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static TreatRValueAsLValue *create(ConstraintSystem &cs,
ConstraintLocator *locator);
};
Expand Down Expand Up @@ -522,6 +526,8 @@ class ContextualMismatch : public ConstraintFix {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;

static ContextualMismatch *create(ConstraintSystem &cs, Type lhs, Type rhs,
ConstraintLocator *locator);
};
Expand Down Expand Up @@ -798,6 +804,10 @@ class UsePropertyWrapper final : public ConstraintFix {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static UsePropertyWrapper *create(ConstraintSystem &cs, VarDecl *wrapped,
bool usingStorageWrapper, Type base,
Type wrapper, ConstraintLocator *locator);
Expand Down Expand Up @@ -825,6 +835,10 @@ class UseWrappedValue final : public ConstraintFix {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static UseWrappedValue *create(ConstraintSystem &cs, VarDecl *propertyWrapper,
Type base, Type wrapper,
ConstraintLocator *locator);
Expand Down Expand Up @@ -1103,6 +1117,8 @@ class AllowFunctionTypeMismatch final : public ContextualMismatch {
bool asNote = false) const override;

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;
};


Expand Down Expand Up @@ -1399,16 +1415,16 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
ConstraintLocator *locator);
};

class RemoveReturn final : public ConstraintFix {
RemoveReturn(ConstraintSystem &cs, ConstraintLocator *locator)
: ConstraintFix(cs, FixKind::RemoveReturn, locator) {}
class RemoveReturn final : public ContextualMismatch {
RemoveReturn(ConstraintSystem &cs, Type resultTy, ConstraintLocator *locator);

public:
std::string getName() const override { return "remove or omit return type"; }

bool diagnose(const Solution &solution, bool asNote = false) const override;

static RemoveReturn *create(ConstraintSystem &cs, ConstraintLocator *locator);
static RemoveReturn *create(ConstraintSystem &cs, Type resultTy,
ConstraintLocator *locator);
};

class CollectionElementContextualMismatch final : public ContextualMismatch {
Expand Down Expand Up @@ -1527,10 +1543,6 @@ class IgnoreContextualType : public ContextualMismatch {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static IgnoreContextualType *create(ConstraintSystem &cs, Type resultTy,
Type specifiedTy,
ConstraintLocator *locator);
Expand All @@ -1548,6 +1560,8 @@ class IgnoreAssignmentDestinationType final : public ContextualMismatch {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;

static IgnoreAssignmentDestinationType *create(ConstraintSystem &cs,
Type sourceTy, Type destTy,
ConstraintLocator *locator);
Expand Down
33 changes: 28 additions & 5 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,20 @@ assessRequirementFailureImpact(ConstraintSystem &cs, Type requirementType,
if (!anchor)
return impact;

// If this is a conditional requirement failure associated with a
// call, let's increase impact of the fix to show that such failure
// makes member/function unreachable in current context or with
// given arguments.
if (auto last = locator.last()) {
if (last->isConditionalRequirement()) {
if (auto *expr = getAsExpr(anchor)) {
auto *parent = cs.getParentExpr(expr);
if (parent && isa<ApplyExpr>(parent))
return 5;
}
}
}

// If this requirement is associated with a member reference and it
// was possible to check it before overload choice is bound, that means
// types came from the context (most likely Self, or associated type(s))
Expand All @@ -1475,17 +1489,21 @@ assessRequirementFailureImpact(ConstraintSystem &cs, Type requirementType,
return 10;
}

// Increase the impact of a conformance fix for a standard library type,
// as it's unlikely to be a good suggestion. Also do the same for the builtin
// compiler types Any and AnyObject, which cannot conform to protocols.
// Increase the impact of a conformance fix for a standard library
// or foundation type, as it's unlikely to be a good suggestion.
//
// Also do the same for the builtin compiler types Any and AnyObject,
// which cannot conform to protocols.
//
// FIXME: We ought not to have the is<TypeVariableType>() condition here, but
// removing it currently regresses the diagnostic for the test case for
// rdar://60727310. Once we better handle the separation of conformance fixes
// from argument mismatches in cases like SR-12438, we should be able to
// remove it from the condition.
auto resolvedTy = cs.simplifyType(requirementType);
if ((requirementType->is<TypeVariableType>() && resolvedTy->isStdlibType()) ||
resolvedTy->isAny() || resolvedTy->isAnyObject()) {
resolvedTy->isAny() || resolvedTy->isAnyObject() ||
getKnownFoundationEntity(resolvedTy->getString())) {
if (auto last = locator.last()) {
if (auto requirement = last->getAs<LocatorPathElt::AnyRequirement>()) {
auto kind = requirement->getRequirementKind();
Expand Down Expand Up @@ -3863,11 +3881,16 @@ bool ConstraintSystem::repairFailures(
if (lhs->isTypeVariableOrMember() || rhs->isTypeVariableOrMember())
break;

// If there is already a fix for contextual failure, let's not
// record a duplicate one.
if (hasFixFor(getConstraintLocator(locator)))
return true;

auto purpose = getContextualTypePurpose(anchor);
if (rhs->isVoid() &&
(purpose == CTP_ReturnStmt || purpose == CTP_ReturnSingleExpr)) {
conversionsOrFixes.push_back(
RemoveReturn::create(*this, getConstraintLocator(locator)));
RemoveReturn::create(*this, lhs, getConstraintLocator(locator)));
return true;
}

Expand Down
Loading