Skip to content

Commit 74a8ee1

Browse files
committed
[Diagnostics] Diagnose missing members via fixes
Try to fix constraint system in a way where member reference is going to be defined in terms of its use, which makes it seem like parameters match arguments exactly. Such helps to produce solutions and diagnose failures related to missing members precisely. These changes would be further extended to diagnose use of unavailable members and other structural member failures. Resolves: rdar://problem/34583132 Resolves: rdar://problem/36989788 Resolved: rdar://problem/39586166 Resolves: rdar://problem/40537782 Resolves: rdar://problem/46211109
1 parent 60fb2bf commit 74a8ee1

17 files changed

+293
-136
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 27 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -523,10 +523,10 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
523523
/// Given a result of name lookup that had no viable results, diagnose the
524524
/// unviable ones.
525525
void diagnoseUnviableLookupResults(MemberLookupResult &lookupResults,
526-
Type baseObjTy, Expr *baseExpr,
526+
Expr *expr, Type baseObjTy, Expr *baseExpr,
527527
DeclName memberName, DeclNameLoc nameLoc,
528528
SourceLoc loc);
529-
529+
530530
/// Produce a diagnostic for a general overload resolution failure
531531
/// (irrespective of the exact expression kind).
532532
bool diagnoseGeneralOverloadFailure(Constraint *constraint);
@@ -913,125 +913,23 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
913913
return;
914914
}
915915

916-
/// When a user refers a enum case with a wrong member name, we try to find a enum
917-
/// element whose name differs from the wrong name only in convention; meaning their
918-
/// lower case counterparts are identical.
919-
/// - DeclName is valid when such a correct case is found; invalid otherwise.
920-
static DeclName
921-
findCorrectEnumCaseName(Type Ty, TypoCorrectionResults &corrections,
922-
DeclName memberName) {
923-
if (memberName.isSpecial() || !memberName.isSimpleName())
924-
return DeclName();
925-
if (!Ty->is<EnumType>() &&
926-
!Ty->is<BoundGenericEnumType>())
927-
return DeclName();
928-
auto candidate =
929-
corrections.getUniqueCandidateMatching([&](ValueDecl *candidate) {
930-
return (isa<EnumElementDecl>(candidate) &&
931-
candidate->getFullName().getBaseIdentifier().str()
932-
.equals_lower(memberName.getBaseIdentifier().str()));
933-
});
934-
return (candidate ? candidate->getFullName() : DeclName());
935-
}
936-
937916
/// Given a result of name lookup that had no viable results, diagnose the
938917
/// unviable ones.
939-
void FailureDiagnosis::
940-
diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
941-
Expr *baseExpr,
942-
DeclName memberName, DeclNameLoc nameLoc,
943-
SourceLoc loc) {
918+
void FailureDiagnosis::diagnoseUnviableLookupResults(
919+
MemberLookupResult &result, Expr *E, Type baseObjTy, Expr *baseExpr,
920+
DeclName memberName, DeclNameLoc nameLoc, SourceLoc loc) {
944921
SourceRange baseRange = baseExpr ? baseExpr->getSourceRange() : SourceRange();
945-
922+
946923
// If we found no results at all, mention that fact.
947924
if (result.UnviableCandidates.empty()) {
948-
TypoCorrectionResults corrections(CS.TC, memberName, nameLoc);
949-
auto tryTypoCorrection = [&] {
950-
CS.TC.performTypoCorrection(CS.DC, DeclRefKind::Ordinary, baseObjTy,
951-
defaultMemberLookupOptions, corrections);
952-
};
953-
954-
// TODO: This should handle tuple member lookups, like x.1231 as well.
955-
if (memberName.getBaseName().getKind() == DeclBaseName::Kind::Subscript) {
956-
diagnose(loc, diag::could_not_find_value_subscript, baseObjTy)
957-
.highlight(baseRange);
958-
} else if (memberName.getBaseName() == "deinit") {
959-
// Specialised diagnostic if trying to access deinitialisers
960-
diagnose(loc, diag::destructor_not_accessible).highlight(baseRange);
961-
} else if (auto metatypeTy = baseObjTy->getAs<MetatypeType>()) {
962-
auto instanceTy = metatypeTy->getInstanceType();
963-
tryTypoCorrection();
964-
965-
if (DeclName rightName = findCorrectEnumCaseName(instanceTy,
966-
corrections,
967-
memberName)) {
968-
diagnose(loc, diag::could_not_find_enum_case, instanceTy,
969-
memberName, rightName)
970-
.fixItReplace(nameLoc.getBaseNameLoc(),
971-
rightName.getBaseIdentifier().str());
972-
return;
973-
}
974-
975-
if (auto correction = corrections.claimUniqueCorrection()) {
976-
auto diagnostic =
977-
diagnose(loc, diag::could_not_find_type_member_corrected,
978-
instanceTy, memberName, correction->CorrectedName);
979-
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
980-
correction->addFixits(diagnostic);
981-
} else {
982-
diagnose(loc, diag::could_not_find_type_member, instanceTy, memberName)
983-
.highlight(baseRange).highlight(nameLoc.getSourceRange());
984-
}
985-
} else if (auto moduleTy = baseObjTy->getAs<ModuleType>()) {
986-
diagnose(baseExpr->getLoc(), diag::no_member_of_module,
987-
moduleTy->getModule()->getName(), memberName)
988-
.highlight(baseRange)
989-
.highlight(nameLoc.getSourceRange());
990-
return;
991-
} else {
992-
auto emitBasicError = [&] {
993-
diagnose(loc, diag::could_not_find_value_member,
994-
baseObjTy, memberName)
995-
.highlight(baseRange).highlight(nameLoc.getSourceRange());
996-
};
997-
998-
// Check for a few common cases that can cause missing members.
999-
if (baseObjTy->is<EnumType>() && memberName.isSimpleName("rawValue")) {
1000-
auto loc = baseObjTy->castTo<EnumType>()->getDecl()->getNameLoc();
1001-
if (loc.isValid()) {
1002-
emitBasicError();
1003-
diagnose(loc, diag::did_you_mean_raw_type);
1004-
return;
1005-
}
1006-
} else if (baseObjTy->isAny()) {
1007-
emitBasicError();
1008-
diagnose(loc, diag::any_as_anyobject_fixit)
1009-
.fixItInsert(baseExpr->getStartLoc(), "(")
1010-
.fixItInsertAfter(baseExpr->getEndLoc(), " as AnyObject)");
1011-
return;
1012-
}
1013-
1014-
tryTypoCorrection();
1015-
1016-
if (auto correction = corrections.claimUniqueCorrection()) {
1017-
auto diagnostic =
1018-
diagnose(loc, diag::could_not_find_value_member_corrected,
1019-
baseObjTy, memberName, correction->CorrectedName);
1020-
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
1021-
correction->addFixits(diagnostic);
1022-
} else {
1023-
emitBasicError();
1024-
}
1025-
}
1026-
1027-
// Note all the correction candidates.
1028-
corrections.noteAllCandidates();
1029-
1030-
// TODO: recover?
925+
MissingMemberFailure failure(nullptr, CS, baseObjTy, memberName,
926+
CS.getConstraintLocator(E));
927+
auto diagnosed = failure.diagnoseAsError();
928+
assert(diagnosed && "Failed to produce missing member diagnostic");
929+
(void)diagnosed;
1031930
return;
1032931
}
1033932

1034-
1035933
// Otherwise, we have at least one (and potentially many) viable candidates
1036934
// sort them out. If all of the candidates have the same problem (commonly
1037935
// because there is exactly one candidate!) diagnose this.
@@ -4632,7 +4530,7 @@ bool FailureDiagnosis::diagnoseMethodAttributeFailures(
46324530
// If one of the unviable candidates matches arguments exactly,
46334531
// that means that actual problem is related to function attributes.
46344532
if (unviableCandidates.closeness == CC_ExactMatch) {
4635-
diagnoseUnviableLookupResults(results, baseType, base, UDE->getName(),
4533+
diagnoseUnviableLookupResults(results, UDE, baseType, base, UDE->getName(),
46364534
UDE->getNameLoc(), UDE->getLoc());
46374535
return true;
46384536
}
@@ -7458,7 +7356,7 @@ bool FailureDiagnosis::diagnoseMemberFailures(
74587356
}
74597357

74607358
// FIXME: Dig out the property DeclNameLoc.
7461-
diagnoseUnviableLookupResults(result, baseObjTy, baseExpr, memberName,
7359+
diagnoseUnviableLookupResults(result, E, baseObjTy, baseExpr, memberName,
74627360
NameLoc, BaseLoc);
74637361
return true;
74647362
}
@@ -8226,6 +8124,20 @@ void FailureDiagnosis::diagnoseAmbiguity(Expr *E) {
82268124
}
82278125
}
82288126

8127+
// Before giving up completely let's try to see if there are any
8128+
// fixes recorded by constraint generator, which point to structural
8129+
// problems that might not result in solution even if fixed e.g.
8130+
// missing members involved in protocol composition in expression
8131+
// context which are interpreted as binary operator expressions instead.
8132+
{
8133+
bool diagnosed = false;
8134+
for (auto *fix : CS.getFixes())
8135+
diagnosed |= fix->diagnose(expr);
8136+
8137+
if (diagnosed)
8138+
return;
8139+
}
8140+
82298141
// If there are no posted constraints or failures, then there was
82308142
// not enough contextual information available to infer a type for the
82318143
// expression.

lib/Sema/CSDiagnostics.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "CSDiagnostics.h"
1818
#include "ConstraintSystem.h"
1919
#include "MiscDiagnostics.h"
20+
#include "TypoCorrection.h"
2021
#include "swift/AST/ASTContext.h"
2122
#include "swift/AST/Decl.h"
2223
#include "swift/AST/Expr.h"
@@ -1332,3 +1333,147 @@ bool SubscriptMisuseFailure::diagnoseAsNote() {
13321333
}
13331334
return false;
13341335
}
1336+
1337+
/// When a user refers a enum case with a wrong member name, we try to find a
1338+
/// enum element whose name differs from the wrong name only in convention;
1339+
/// meaning their lower case counterparts are identical.
1340+
/// - DeclName is valid when such a correct case is found; invalid otherwise.
1341+
DeclName MissingMemberFailure::findCorrectEnumCaseName(
1342+
Type Ty, TypoCorrectionResults &corrections, DeclName memberName) {
1343+
if (memberName.isSpecial() || !memberName.isSimpleName())
1344+
return DeclName();
1345+
if (!Ty->getEnumOrBoundGenericEnum())
1346+
return DeclName();
1347+
auto candidate =
1348+
corrections.getUniqueCandidateMatching([&](ValueDecl *candidate) {
1349+
return (isa<EnumElementDecl>(candidate) &&
1350+
candidate->getFullName().getBaseIdentifier().str().equals_lower(
1351+
memberName.getBaseIdentifier().str()));
1352+
});
1353+
return (candidate ? candidate->getFullName() : DeclName());
1354+
}
1355+
1356+
bool MissingMemberFailure::diagnoseAsError() {
1357+
auto &TC = getTypeChecker();
1358+
auto *anchor = getRawAnchor();
1359+
auto *baseExpr = getAnchor();
1360+
1361+
if (!anchor || !baseExpr)
1362+
return false;
1363+
1364+
if (auto *typeVar = BaseType->getAs<TypeVariableType>()) {
1365+
auto &CS = getConstraintSystem();
1366+
auto *memberLoc = typeVar->getImpl().getLocator();
1367+
// Don't try to diagnose anything besides first missing
1368+
// member in the chain. e.g. `x.foo().bar()` let's make
1369+
// sure to diagnose only `foo()` as missing because we
1370+
// don't really know much about what `bar()` is supposed
1371+
// to be.
1372+
if (CS.MissingMembers.count(memberLoc))
1373+
return false;
1374+
}
1375+
1376+
auto baseType = resolveType(BaseType)->getWithoutSpecifierType();
1377+
1378+
DeclNameLoc nameLoc(anchor->getStartLoc());
1379+
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(anchor)) {
1380+
nameLoc = UDE->getNameLoc();
1381+
} else if (auto *UME = dyn_cast<UnresolvedMemberExpr>(anchor)) {
1382+
nameLoc = UME->getNameLoc();
1383+
}
1384+
1385+
auto emitBasicError = [&](Type baseType) {
1386+
auto diagnostic = diag::could_not_find_value_member;
1387+
1388+
if (auto *metatype = baseType->getAs<MetatypeType>()) {
1389+
baseType = metatype->getInstanceType();
1390+
diagnostic = diag::could_not_find_type_member;
1391+
}
1392+
1393+
if (baseType->is<TupleType>())
1394+
diagnostic = diag::could_not_find_tuple_member;
1395+
1396+
emitDiagnostic(anchor->getLoc(), diagnostic, baseType, Name)
1397+
.highlight(baseExpr->getSourceRange())
1398+
.highlight(nameLoc.getSourceRange());
1399+
};
1400+
1401+
TypoCorrectionResults corrections(TC, Name, nameLoc);
1402+
auto tryTypoCorrection = [&] {
1403+
TC.performTypoCorrection(getDC(), DeclRefKind::Ordinary, baseType,
1404+
defaultMemberLookupOptions, corrections);
1405+
};
1406+
1407+
if (Name.getBaseName().getKind() == DeclBaseName::Kind::Subscript) {
1408+
emitDiagnostic(anchor->getLoc(), diag::could_not_find_value_subscript,
1409+
baseType)
1410+
.highlight(baseExpr->getSourceRange());
1411+
} else if (Name.getBaseName() == "deinit") {
1412+
// Specialised diagnostic if trying to access deinitialisers
1413+
emitDiagnostic(anchor->getLoc(), diag::destructor_not_accessible)
1414+
.highlight(baseExpr->getSourceRange());
1415+
} else if (auto metatypeTy = baseType->getAs<MetatypeType>()) {
1416+
auto instanceTy = metatypeTy->getInstanceType();
1417+
tryTypoCorrection();
1418+
1419+
if (DeclName rightName =
1420+
findCorrectEnumCaseName(instanceTy, corrections, Name)) {
1421+
emitDiagnostic(anchor->getLoc(), diag::could_not_find_enum_case,
1422+
instanceTy, Name, rightName)
1423+
.fixItReplace(nameLoc.getBaseNameLoc(),
1424+
rightName.getBaseIdentifier().str());
1425+
return true;
1426+
}
1427+
1428+
if (auto correction = corrections.claimUniqueCorrection()) {
1429+
auto diagnostic = emitDiagnostic(
1430+
anchor->getLoc(), diag::could_not_find_type_member_corrected,
1431+
instanceTy, Name, correction->CorrectedName);
1432+
diagnostic.highlight(baseExpr->getSourceRange())
1433+
.highlight(nameLoc.getSourceRange());
1434+
correction->addFixits(diagnostic);
1435+
} else {
1436+
emitBasicError(baseType);
1437+
}
1438+
} else if (auto moduleTy = baseType->getAs<ModuleType>()) {
1439+
emitDiagnostic(baseExpr->getLoc(), diag::no_member_of_module,
1440+
moduleTy->getModule()->getName(), Name)
1441+
.highlight(baseExpr->getSourceRange())
1442+
.highlight(nameLoc.getSourceRange());
1443+
return true;
1444+
} else {
1445+
// Check for a few common cases that can cause missing members.
1446+
auto *ED = baseType->getEnumOrBoundGenericEnum();
1447+
if (ED && Name.isSimpleName("rawValue")) {
1448+
auto loc = ED->getNameLoc();
1449+
if (loc.isValid()) {
1450+
emitBasicError(baseType);
1451+
emitDiagnostic(loc, diag::did_you_mean_raw_type);
1452+
return true;
1453+
}
1454+
} else if (baseType->isAny()) {
1455+
emitBasicError(baseType);
1456+
emitDiagnostic(anchor->getLoc(), diag::any_as_anyobject_fixit)
1457+
.fixItInsert(baseExpr->getStartLoc(), "(")
1458+
.fixItInsertAfter(baseExpr->getEndLoc(), " as AnyObject)");
1459+
return true;
1460+
}
1461+
1462+
tryTypoCorrection();
1463+
1464+
if (auto correction = corrections.claimUniqueCorrection()) {
1465+
auto diagnostic = emitDiagnostic(
1466+
anchor->getLoc(), diag::could_not_find_value_member_corrected,
1467+
baseType, Name, correction->CorrectedName);
1468+
diagnostic.highlight(baseExpr->getSourceRange())
1469+
.highlight(nameLoc.getSourceRange());
1470+
correction->addFixits(diagnostic);
1471+
} else {
1472+
emitBasicError(baseType);
1473+
}
1474+
}
1475+
1476+
// Note all the correction candidates.
1477+
corrections.noteAllCandidates();
1478+
return true;
1479+
}

lib/Sema/CSDiagnostics.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/AST/Decl.h"
2424
#include "swift/AST/DiagnosticEngine.h"
2525
#include "swift/AST/Expr.h"
26+
#include "swift/AST/Identifier.h"
2627
#include "swift/AST/Types.h"
2728
#include "swift/Basic/SourceLoc.h"
2829
#include "llvm/ADT/ArrayRef.h"
@@ -646,6 +647,33 @@ class SubscriptMisuseFailure final : public FailureDiagnostic {
646647
bool diagnoseAsNote() override;
647648
};
648649

650+
/// Diagnose situations when member referenced by name is missing
651+
/// from the associated base type, e.g.
652+
///
653+
/// ```swift
654+
/// struct S {}
655+
/// func foo(_ s: S) {
656+
/// let _: Int = s.foo(1, 2) // expected type is `(Int, Int) -> Int`
657+
/// }
658+
/// ```
659+
class MissingMemberFailure final : public FailureDiagnostic {
660+
Type BaseType;
661+
DeclName Name;
662+
663+
public:
664+
MissingMemberFailure(Expr *root, ConstraintSystem &cs, Type baseType,
665+
DeclName memberName, ConstraintLocator *locator)
666+
: FailureDiagnostic(root, cs, locator), BaseType(baseType),
667+
Name(memberName) {}
668+
669+
bool diagnoseAsError() override;
670+
671+
private:
672+
static DeclName findCorrectEnumCaseName(Type Ty,
673+
TypoCorrectionResults &corrections,
674+
DeclName memberName);
675+
};
676+
649677
} // end namespace constraints
650678
} // end namespace swift
651679

lib/Sema/CSFix.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ UseSubscriptOperator *UseSubscriptOperator::create(ConstraintSystem &cs,
247247
}
248248

249249
bool DefineMemberBasedOnUse::diagnose(Expr *root, bool asNote) const {
250-
return false;
250+
auto failure = MissingMemberFailure(root, getConstraintSystem(), BaseType,
251+
Name, getLocator());
252+
return failure.diagnose(asNote);
251253
}
252254

253255
DefineMemberBasedOnUse *

0 commit comments

Comments
 (0)