Skip to content

Commit 3a23c08

Browse files
committed
Implement typo correction for #keypath expressions.
1 parent cac8053 commit 3a23c08

File tree

6 files changed

+87
-14
lines changed

6 files changed

+87
-14
lines changed

lib/AST/LookupVisibleDecls.cpp

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,47 @@ static void doDynamicLookup(VisibleDeclConsumer &Consumer,
293293
if (D->getOverriddenDecl())
294294
return;
295295

296+
// Ensure that the declaration has a type.
297+
if (!D->hasType()) {
298+
if (!TypeResolver) return;
299+
TypeResolver->resolveDeclSignature(D);
300+
if (!D->hasType()) return;
301+
}
302+
303+
switch (D->getKind()) {
304+
#define DECL(ID, SUPER) \
305+
case DeclKind::ID:
306+
#define VALUE_DECL(ID, SUPER)
307+
#include "swift/AST/DeclNodes.def"
308+
llvm_unreachable("not a ValueDecl!");
309+
310+
// Types cannot be found by dynamic lookup.
311+
case DeclKind::GenericTypeParam:
312+
case DeclKind::AssociatedType:
313+
case DeclKind::TypeAlias:
314+
case DeclKind::Enum:
315+
case DeclKind::Class:
316+
case DeclKind::Struct:
317+
case DeclKind::Protocol:
318+
return;
319+
296320
// Initializers cannot be found by dynamic lookup.
297-
if (isa<ConstructorDecl>(D))
321+
case DeclKind::Constructor:
322+
case DeclKind::Destructor:
323+
return;
324+
325+
// These cases are probably impossible here but can also just
326+
// be safely ignored.
327+
case DeclKind::EnumElement:
328+
case DeclKind::Param:
329+
case DeclKind::Module:
298330
return;
299331

300-
// Check if we already reported a decl with the same signature.
301-
if (auto *FD = dyn_cast<FuncDecl>(D)) {
332+
// For other kinds of values, check if we already reported a decl
333+
// with the same signature.
334+
335+
case DeclKind::Func: {
336+
auto FD = cast<FuncDecl>(D);
302337
assert(FD->getImplicitSelfDecl() && "should not find free functions");
303338
(void)FD;
304339

@@ -311,17 +346,23 @@ static void doDynamicLookup(VisibleDeclConsumer &Consumer,
311346
auto Signature = std::make_pair(D->getName(), T);
312347
if (!FunctionsReported.insert(Signature).second)
313348
return;
314-
} else if (isa<SubscriptDecl>(D)) {
349+
break;
350+
}
351+
352+
case DeclKind::Subscript: {
315353
auto Signature = D->getType()->getCanonicalType();
316354
if (!SubscriptsReported.insert(Signature).second)
317355
return;
318-
} else if (isa<VarDecl>(D)) {
356+
break;
357+
}
358+
359+
case DeclKind::Var: {
319360
auto Signature =
320361
std::make_pair(D->getName(), D->getType()->getCanonicalType());
321362
if (!PropertiesReported.insert(Signature).second)
322363
return;
323-
} else {
324-
llvm_unreachable("unhandled decl kind");
364+
break;
365+
}
325366
}
326367

327368
if (isDeclVisibleInLookupMode(D, LS, CurrDC, TypeResolver))

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,8 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
458458
// one, but we should also try to propagate labels into this.
459459
DeclNameLoc nameLoc = UDRE->getNameLoc();
460460

461-
performTypoCorrection(DC, UDRE->getRefKind(), Name, Loc, LookupOptions,
462-
Lookup);
461+
performTypoCorrection(DC, UDRE->getRefKind(), Type(), Name, Loc,
462+
LookupOptions, Lookup);
463463

464464
diagnose(Loc, diag::use_unresolved_identifier, Name, Name.isOperator())
465465
.highlight(UDRE->getSourceRange());

lib/Sema/TypeCheckExprObjC.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,17 +212,33 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
212212
// Look for this component.
213213
LookupResult lookup = performLookup(idx, componentName, componentNameLoc);
214214

215-
// If we didn't find anything, complain and bail out.
215+
// If we didn't find anything, try to apply typo-correction.
216+
bool resultsAreFromTypoCorrection = false;
216217
if (!lookup) {
217-
// FIXME: Typo correction.
218+
performTypoCorrection(dc, DeclRefKind::Ordinary, currentType,
219+
componentName, componentNameLoc,
220+
(currentType ? defaultMemberTypeLookupOptions
221+
: defaultUnqualifiedLookupOptions),
222+
lookup);
223+
218224
if (currentType)
219225
diagnose(componentNameLoc, diag::could_not_find_type_member,
220226
currentType, componentName);
221227
else
222228
diagnose(componentNameLoc, diag::use_unresolved_identifier,
223229
componentName, false);
230+
231+
// Note all the correction candidates.
232+
for (auto &result : lookup) {
233+
noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
234+
result);
235+
}
236+
224237
isInvalid = true;
225-
break;
238+
if (!lookup) break;
239+
240+
// Remember that these are from typo correction.
241+
resultsAreFromTypoCorrection = true;
226242
}
227243

228244
// If we have more than one result, filter out unavailable or
@@ -243,6 +259,10 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
243259

244260
// If we *still* have more than one result, fail.
245261
if (lookup.size() > 1) {
262+
// Don't diagnose ambiguities if the results are from typo correction.
263+
if (resultsAreFromTypoCorrection)
264+
break;
265+
246266
if (currentType)
247267
diagnose(componentNameLoc, diag::ambiguous_member_overload_set,
248268
componentName);

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ namespace {
518518
}
519519

520520
void TypeChecker::performTypoCorrection(DeclContext *DC, DeclRefKind refKind,
521+
Type baseTypeOrNull,
521522
DeclName targetDeclName,
522523
SourceLoc nameLoc,
523524
NameLookupOptions lookupOptions,
@@ -554,7 +555,13 @@ void TypeChecker::performTypoCorrection(DeclContext *DC, DeclRefKind refKind,
554555
});
555556

556557
TypoCorrectionResolver resolver(*this, nameLoc);
557-
lookupVisibleDecls(consumer, DC, &resolver, /*top level*/ true, nameLoc);
558+
if (baseTypeOrNull) {
559+
lookupVisibleMemberDecls(consumer, baseTypeOrNull, DC, &resolver,
560+
/*include instance members*/
561+
!(lookupOptions & NameLookupFlags::OnlyTypes));
562+
} else {
563+
lookupVisibleDecls(consumer, DC, &resolver, /*top level*/ true, nameLoc);
564+
}
558565

559566
// Impose a maximum distance from the best score.
560567
entries.filterMaxScoreRange(MaxCallEditDistanceFromBestCandidate);

lib/Sema/TypeChecker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,7 @@ class TypeChecker final : public LazyResolver {
19591959
/// Check for a typo correction.
19601960
void performTypoCorrection(DeclContext *DC,
19611961
DeclRefKind refKind,
1962+
Type baseTypeOrNull,
19621963
DeclName name,
19631964
SourceLoc lookupLoc,
19641965
NameLookupOptions lookupOptions,

test/expr/unary/keypath/keypath.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Foundation
66

77
@objc class A : NSObject {
88
@objc var propB: B = B()
9-
@objc var propString: String = ""
9+
@objc var propString: String = "" // expected-note {{did you mean 'propString'}}
1010
@objc var propArray: [String] = []
1111
@objc var propDict: [String: B] = [:]
1212
@objc var propSet: Set<String> = []
@@ -110,3 +110,7 @@ func testParseErrors() {
110110
let _: String = #keyPath(A.propString; // expected-error{{expected ')' to complete '#keyPath' expression}}
111111
// expected-note@-1{{to match this opening '('}}
112112
}
113+
114+
func testTypoCorrection() {
115+
let _: String = #keyPath(A.proString) // expected-error {{type 'A' has no member 'proString'}}
116+
}

0 commit comments

Comments
 (0)