Skip to content

Commit 7ee32d4

Browse files
authored
Merge pull request #15800 from rjmccall/typo-correction-error-fixit
Add unique typo corrections to the main diagnostic with a fix-it
2 parents 9e99423 + 7815892 commit 7ee32d4

27 files changed

+423
-194
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
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

Lines changed: 13 additions & 8 deletions
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

Lines changed: 50 additions & 35 deletions
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

Lines changed: 25 additions & 12 deletions
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

Lines changed: 6 additions & 6 deletions
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

Lines changed: 8 additions & 11 deletions
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)