Skip to content

Commit 7815892

Browse files
committed
Add unique typo corrections to the main diagnostic with a fix-it.
Continue to emit notes for the candidates, but use different text. Note that we can emit a typo correction fix-it even if there are multiple candidates with the same name. Also, disable typo correction in the migrator, since the operation is quite expensive, the notes are never presented to the user, and the fix-its can interfere with the migrator's own edits. Our general guidance is that fix-its should be added on the main diagnostic only when the fix-it is highly likely to be correct. The exact threshold is debateable. Typo correction is certainly capable of making mistakes, but most of its edits are right, and when it's wrong it's usually obviously wrong. On balance, I think this is the right thing to do. For what it's worth, it's also what we do in Clang.
1 parent 478d846 commit 7815892

27 files changed

+423
-194
lines changed

include/swift/AST/DiagnosticsSema.def

+11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ NOTE(type_declared_here,none,
4242
"type declared here", ())
4343
NOTE(decl_declared_here,none,
4444
"%0 declared here", (DeclName))
45+
NOTE(implicit_member_declared_here,none,
46+
"%1 '%0' is implicitly declared", (StringRef, StringRef))
4547
NOTE(extended_type_declared_here,none,
4648
"extended type declared here", ())
4749

@@ -64,8 +66,14 @@ ERROR(could_not_find_tuple_member,none,
6466

6567
ERROR(could_not_find_value_member,none,
6668
"value of type %0 has no member %1", (Type, DeclName))
69+
ERROR(could_not_find_value_member_corrected,none,
70+
"value of type %0 has no member %1; did you mean %2?",
71+
(Type, DeclName, DeclName))
6772
ERROR(could_not_find_type_member,none,
6873
"type %0 has no member %1", (Type, DeclName))
74+
ERROR(could_not_find_type_member_corrected,none,
75+
"type %0 has no member %1; did you mean %2?",
76+
(Type, DeclName, DeclName))
6977

7078
ERROR(could_not_find_enum_case,none,
7179
"enum type %0 has no case %1; did you mean %2", (Type, DeclName, DeclName))
@@ -654,6 +662,9 @@ ERROR(unspaced_unary_operator,none,
654662

655663
ERROR(use_unresolved_identifier,none,
656664
"use of unresolved %select{identifier|operator}1 %0", (DeclName, bool))
665+
ERROR(use_unresolved_identifier_corrected,none,
666+
"use of unresolved %select{identifier|operator}1 %0; did you mean %2?",
667+
(DeclName, bool, DeclName))
657668
NOTE(confusable_character,none,
658669
"%select{identifier|operator}0 '%1' contains possibly confused characters; "
659670
"did you mean to use '%2'?",

lib/Frontend/CompilerInvocation.cpp

+13-8
Original file line numberDiff line numberDiff line change
@@ -920,12 +920,11 @@ static std::string getScriptFileName(StringRef name, bool isSwiftVersion3) {
920920
return (Twine(name) + langVer + ".json").str();
921921
}
922922

923-
bool ParseMigratorArgs(MigratorOptions &Opts,
924-
const FrontendOptions &FrontendOpts,
925-
const llvm::Triple &Triple,
926-
const bool isSwiftVersion3,
927-
StringRef ResourcePath, const ArgList &Args,
928-
DiagnosticEngine &Diags) {
923+
static bool ParseMigratorArgs(MigratorOptions &Opts,
924+
LangOptions &LangOpts,
925+
const FrontendOptions &FrontendOpts,
926+
StringRef ResourcePath, const ArgList &Args,
927+
DiagnosticEngine &Diags) {
929928
using namespace options;
930929

931930
Opts.KeepObjcVisibility |= Args.hasArg(OPT_migrate_keep_objc_visibility);
@@ -950,9 +949,13 @@ bool ParseMigratorArgs(MigratorOptions &Opts,
950949
if (auto DataPath = Args.getLastArg(OPT_api_diff_data_file)) {
951950
Opts.APIDigesterDataStorePaths.push_back(DataPath->getValue());
952951
} else {
952+
auto &Triple = LangOpts.Target;
953+
bool isSwiftVersion3 = LangOpts.isSwiftVersion3();
954+
953955
bool Supported = true;
954956
llvm::SmallString<128> dataPath(ResourcePath);
955957
llvm::sys::path::append(dataPath, "migrator");
958+
956959
if (Triple.isMacOSX())
957960
llvm::sys::path::append(dataPath,
958961
getScriptFileName("macos", isSwiftVersion3));
@@ -991,6 +994,9 @@ bool ParseMigratorArgs(MigratorOptions &Opts,
991994
// supplementary output for the whole compilation instead of one per input,
992995
// so it's probably not worth it.
993996
FrontendOpts.InputsAndOutputs.assertMustNotBeMoreThanOnePrimaryInput();
997+
998+
// Always disable typo-correction in the migrator.
999+
LangOpts.TypoCorrectionLimit = 0;
9941000
}
9951001

9961002
return false;
@@ -1061,8 +1067,7 @@ bool CompilerInvocation::parseArgs(
10611067
return true;
10621068
}
10631069

1064-
if (ParseMigratorArgs(MigratorOpts, FrontendOpts, LangOpts.Target,
1065-
LangOpts.isSwiftVersion3(),
1070+
if (ParseMigratorArgs(MigratorOpts, LangOpts, FrontendOpts,
10661071
SearchPathOpts.RuntimeResourcePath, ParsedArgs, Diags)) {
10671072
return true;
10681073
}

lib/Sema/CSDiag.cpp

+50-35
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "CSDiag.h"
1919
#include "CalleeCandidateInfo.h"
2020
#include "MiscDiagnostics.h"
21+
#include "TypoCorrection.h"
2122
#include "swift/AST/ASTWalker.h"
2223
#include "swift/AST/GenericEnvironment.h"
2324
#include "swift/AST/Initializer.h"
@@ -1330,25 +1331,20 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
13301331
/// lower case counterparts are identical.
13311332
/// - DeclName is valid when such a correct case is found; invalid otherwise.
13321333
static DeclName
1333-
findCorrectEnumCaseName(Type Ty, LookupResult &Result,
1334+
findCorrectEnumCaseName(Type Ty, TypoCorrectionResults &corrections,
13341335
DeclName memberName) {
13351336
if (!memberName.isSimpleName())
13361337
return DeclName();
13371338
if (!Ty->is<EnumType>() &&
13381339
!Ty->is<BoundGenericEnumType>())
13391340
return DeclName();
1340-
llvm::SmallVector<DeclName, 4> candidates;
1341-
for (auto &correction : Result) {
1342-
DeclName correctName = correction.getValueDecl()->getFullName();
1343-
if (!isa<EnumElementDecl>(correction.getValueDecl()))
1344-
continue;
1345-
if (correctName.getBaseIdentifier().str().equals_lower(
1346-
memberName.getBaseIdentifier().str()))
1347-
candidates.push_back(correctName.getBaseName());
1348-
}
1349-
if (candidates.size() == 1)
1350-
return candidates.front();
1351-
return DeclName();
1341+
auto candidate =
1342+
corrections.getUniqueCandidateMatching([&](ValueDecl *candidate) {
1343+
return (isa<EnumElementDecl>(candidate) &&
1344+
candidate->getFullName().getBaseIdentifier().str()
1345+
.equals_lower(memberName.getBaseIdentifier().str()));
1346+
});
1347+
return (candidate ? candidate->getFullName() : DeclName());
13521348
}
13531349

13541350
/// Given a result of name lookup that had no viable results, diagnose the
@@ -1362,12 +1358,10 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
13621358

13631359
// If we found no results at all, mention that fact.
13641360
if (result.UnviableCandidates.empty()) {
1365-
LookupResult correctionResults;
1361+
TypoCorrectionResults corrections(CS.TC, memberName, nameLoc);
13661362
auto tryTypoCorrection = [&] {
13671363
CS.TC.performTypoCorrection(CS.DC, DeclRefKind::Ordinary, baseObjTy,
1368-
memberName, nameLoc.getBaseNameLoc(),
1369-
defaultMemberLookupOptions,
1370-
correctionResults);
1364+
defaultMemberLookupOptions, corrections);
13711365
};
13721366

13731367
// TODO: This should handle tuple member lookups, like x.1231 as well.
@@ -1382,48 +1376,69 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
13821376
tryTypoCorrection();
13831377

13841378
if (DeclName rightName = findCorrectEnumCaseName(instanceTy,
1385-
correctionResults,
1379+
corrections,
13861380
memberName)) {
13871381
diagnose(loc, diag::could_not_find_enum_case, instanceTy,
13881382
memberName, rightName)
13891383
.fixItReplace(nameLoc.getBaseNameLoc(),
13901384
rightName.getBaseIdentifier().str());
13911385
return;
13921386
}
1393-
diagnose(loc, diag::could_not_find_type_member, instanceTy, memberName)
1394-
.highlight(baseRange).highlight(nameLoc.getSourceRange());
1387+
1388+
if (auto correction = corrections.claimUniqueCorrection()) {
1389+
auto diagnostic =
1390+
diagnose(loc, diag::could_not_find_type_member_corrected,
1391+
instanceTy, memberName, correction->CorrectedName);
1392+
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
1393+
correction->addFixits(diagnostic);
1394+
} else {
1395+
diagnose(loc, diag::could_not_find_type_member, instanceTy, memberName)
1396+
.highlight(baseRange).highlight(nameLoc.getSourceRange());
1397+
}
13951398
} else if (auto moduleTy = baseObjTy->getAs<ModuleType>()) {
13961399
diagnose(baseExpr->getLoc(), diag::no_member_of_module,
13971400
moduleTy->getModule()->getName(), memberName)
13981401
.highlight(baseRange)
13991402
.highlight(nameLoc.getSourceRange());
14001403
return;
14011404
} else {
1402-
diagnose(loc, diag::could_not_find_value_member,
1403-
baseObjTy, memberName)
1404-
.highlight(baseRange).highlight(nameLoc.getSourceRange());
1405-
tryTypoCorrection();
1405+
auto emitBasicError = [&] {
1406+
diagnose(loc, diag::could_not_find_value_member,
1407+
baseObjTy, memberName)
1408+
.highlight(baseRange).highlight(nameLoc.getSourceRange());
1409+
};
14061410

14071411
// Check for a few common cases that can cause missing members.
14081412
if (baseObjTy->is<EnumType>() && memberName.isSimpleName("rawValue")) {
14091413
auto loc = baseObjTy->castTo<EnumType>()->getDecl()->getNameLoc();
14101414
if (loc.isValid()) {
1415+
emitBasicError();
14111416
diagnose(loc, diag::did_you_mean_raw_type);
1412-
return; // Always prefer this over typo corrections.
1417+
return;
14131418
}
14141419
} else if (baseObjTy->isAny()) {
1420+
emitBasicError();
14151421
diagnose(loc, diag::any_as_anyobject_fixit)
14161422
.fixItInsert(baseExpr->getStartLoc(), "(")
14171423
.fixItInsertAfter(baseExpr->getEndLoc(), " as AnyObject)");
14181424
return;
14191425
}
1426+
1427+
tryTypoCorrection();
1428+
1429+
if (auto correction = corrections.claimUniqueCorrection()) {
1430+
auto diagnostic =
1431+
diagnose(loc, diag::could_not_find_value_member_corrected,
1432+
baseObjTy, memberName, correction->CorrectedName);
1433+
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
1434+
correction->addFixits(diagnostic);
1435+
} else {
1436+
emitBasicError();
1437+
}
14201438
}
14211439

14221440
// Note all the correction candidates.
1423-
for (auto &correction : correctionResults) {
1424-
CS.TC.noteTypoCorrection(memberName, nameLoc,
1425-
correction.getValueDecl());
1426-
}
1441+
corrections.noteAllCandidates();
14271442

14281443
// TODO: recover?
14291444
return;
@@ -6803,11 +6818,13 @@ static bool diagnoseKeyPathComponents(ConstraintSystem &CS, KeyPathExpr *KPE,
68036818
// If we didn't find anything, try to apply typo-correction.
68046819
bool resultsAreFromTypoCorrection = false;
68056820
if (!lookup) {
6821+
TypoCorrectionResults corrections(TC, componentName,
6822+
DeclNameLoc(componentNameLoc));
6823+
68066824
TC.performTypoCorrection(CS.DC, DeclRefKind::Ordinary, lookupType,
6807-
componentName, componentNameLoc,
68086825
(lookupType ? defaultMemberTypeLookupOptions
68096826
: defaultUnqualifiedLookupOptions),
6810-
lookup);
6827+
corrections);
68116828

68126829
if (currentType)
68136830
TC.diagnose(componentNameLoc, diag::could_not_find_type_member,
@@ -6817,10 +6834,8 @@ static bool diagnoseKeyPathComponents(ConstraintSystem &CS, KeyPathExpr *KPE,
68176834
componentName, false);
68186835

68196836
// Note all the correction candidates.
6820-
for (auto &result : lookup) {
6821-
TC.noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
6822-
result.getValueDecl());
6823-
}
6837+
corrections.noteAllCandidates();
6838+
corrections.addAllCandidatesToLookup(lookup);
68246839

68256840
isInvalid = true;
68266841
if (!lookup)

lib/Sema/TypeCheckConstraints.cpp

+25-12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "ConstraintSystem.h"
2020
#include "GenericTypeResolver.h"
2121
#include "TypeChecker.h"
22+
#include "TypoCorrection.h"
2223
#include "MiscDiagnostics.h"
2324
#include "swift/AST/ASTVisitor.h"
2425
#include "swift/AST/ASTWalker.h"
@@ -448,12 +449,6 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
448449
// one, but we should also try to propagate labels into this.
449450
DeclNameLoc nameLoc = UDRE->getNameLoc();
450451

451-
performTypoCorrection(DC, UDRE->getRefKind(), Type(), Name, Loc,
452-
lookupOptions, Lookup);
453-
454-
diagnose(Loc, diag::use_unresolved_identifier, Name, Name.isOperator())
455-
.highlight(UDRE->getSourceRange());
456-
457452
Identifier simpleName = Name.getBaseIdentifier();
458453
const char *buffer = simpleName.get();
459454
llvm::SmallString<64> expectedIdentifier;
@@ -477,16 +472,34 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
477472
offset += length;
478473
}
479474

480-
if (isConfused) {
475+
auto emitBasicError = [&] {
476+
diagnose(Loc, diag::use_unresolved_identifier, Name, Name.isOperator())
477+
.highlight(UDRE->getSourceRange());
478+
};
479+
480+
bool claimed = false;
481+
if (!isConfused) {
482+
TypoCorrectionResults corrections(*this, Name, nameLoc);
483+
performTypoCorrection(DC, UDRE->getRefKind(), Type(),
484+
lookupOptions, corrections);
485+
486+
if (auto typo = corrections.claimUniqueCorrection()) {
487+
auto diag = diagnose(Loc, diag::use_unresolved_identifier_corrected,
488+
Name, Name.isOperator(), typo->CorrectedName);
489+
diag.highlight(UDRE->getSourceRange());
490+
typo->addFixits(diag);
491+
} else {
492+
emitBasicError();
493+
}
494+
495+
corrections.noteAllCandidates();
496+
} else {
497+
emitBasicError();
498+
481499
diagnose(Loc, diag::confusable_character,
482500
UDRE->getName().isOperator(), simpleName.str(),
483501
expectedIdentifier)
484502
.fixItReplace(Loc, expectedIdentifier);
485-
} else {
486-
// Note all the correction candidates.
487-
for (auto &result : Lookup) {
488-
noteTypoCorrection(Name, nameLoc, result.getValueDecl());
489-
}
490503
}
491504

492505
// TODO: consider recovering from here. We may want some way to suppress

lib/Sema/TypeCheckExprObjC.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//
1616
//===----------------------------------------------------------------------===//
1717
#include "TypeChecker.h"
18+
#include "TypoCorrection.h"
1819
#include "swift/Basic/Range.h"
1920

2021
using namespace swift;
@@ -248,11 +249,12 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
248249
// If we didn't find anything, try to apply typo-correction.
249250
bool resultsAreFromTypoCorrection = false;
250251
if (!lookup) {
252+
TypoCorrectionResults corrections(*this, componentName,
253+
DeclNameLoc(componentNameLoc));
251254
performTypoCorrection(dc, DeclRefKind::Ordinary, lookupType,
252-
componentName, componentNameLoc,
253255
(lookupType ? defaultMemberTypeLookupOptions
254256
: defaultUnqualifiedLookupOptions),
255-
lookup);
257+
corrections);
256258

257259
if (currentType)
258260
diagnose(componentNameLoc, diag::could_not_find_type_member,
@@ -262,10 +264,8 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
262264
componentName, false);
263265

264266
// Note all the correction candidates.
265-
for (auto &result : lookup) {
266-
noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
267-
result.getValueDecl());
268-
}
267+
corrections.noteAllCandidates();
268+
corrections.addAllCandidatesToLookup(lookup);
269269

270270
isInvalid = true;
271271
if (!lookup) break;

lib/Sema/TypeCheckGeneric.cpp

+8-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616
#include "TypeChecker.h"
1717
#include "GenericTypeResolver.h"
18+
#include "TypoCorrection.h"
1819
#include "swift/AST/GenericEnvironment.h"
1920
#include "swift/AST/GenericSignatureBuilder.h"
2021
#include "swift/AST/ProtocolConformance.h"
@@ -147,30 +148,26 @@ Type CompleteGenericTypeResolver::resolveDependentMemberType(
147148
} else {
148149
// Resolve the base to a potential archetype.
149150
// Perform typo correction.
150-
LookupResult corrections;
151+
TypoCorrectionResults corrections(tc, ref->getIdentifier(),
152+
DeclNameLoc(ref->getIdLoc()));
151153
tc.performTypoCorrection(DC, DeclRefKind::Ordinary,
152154
MetatypeType::get(baseTy),
153-
ref->getIdentifier(), ref->getIdLoc(),
154155
NameLookupFlags::ProtocolMembers,
155156
corrections, &builder);
156157

157-
// Filter out non-types.
158-
corrections.filter([](const LookupResultEntry &result) {
159-
return isa<TypeDecl>(result.getValueDecl());
160-
});
161-
162158
// Check whether we have a single type result.
163-
auto singleType = corrections.getSingleTypeResult();
159+
auto singleType = cast_or_null<TypeDecl>(
160+
corrections.getUniqueCandidateMatching([](ValueDecl *result) {
161+
return isa<TypeDecl>(result);
162+
}));
164163

165164
// If we don't have a single result, complain and fail.
166165
if (!singleType) {
167166
Identifier name = ref->getIdentifier();
168167
SourceLoc nameLoc = ref->getIdLoc();
169168
tc.diagnose(nameLoc, diag::invalid_member_type, name, baseTy)
170169
.highlight(baseRange);
171-
for (const auto &suggestion : corrections)
172-
tc.noteTypoCorrection(name, DeclNameLoc(nameLoc),
173-
suggestion.getValueDecl());
170+
corrections.noteAllCandidates();
174171

175172
return ErrorType::get(tc.Context);
176173
}

0 commit comments

Comments
 (0)