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.
This commit is contained in:
John McCall
2018-04-06 16:32:07 -04:00
parent 478d846e36
commit 7815892a76
27 changed files with 425 additions and 196 deletions

View File

@@ -42,6 +42,8 @@ NOTE(type_declared_here,none,
"type declared here", ())
NOTE(decl_declared_here,none,
"%0 declared here", (DeclName))
NOTE(implicit_member_declared_here,none,
"%1 '%0' is implicitly declared", (StringRef, StringRef))
NOTE(extended_type_declared_here,none,
"extended type declared here", ())
@@ -64,8 +66,14 @@ ERROR(could_not_find_tuple_member,none,
ERROR(could_not_find_value_member,none,
"value of type %0 has no member %1", (Type, DeclName))
ERROR(could_not_find_value_member_corrected,none,
"value of type %0 has no member %1; did you mean %2?",
(Type, DeclName, DeclName))
ERROR(could_not_find_type_member,none,
"type %0 has no member %1", (Type, DeclName))
ERROR(could_not_find_type_member_corrected,none,
"type %0 has no member %1; did you mean %2?",
(Type, DeclName, DeclName))
ERROR(could_not_find_enum_case,none,
"enum type %0 has no case %1; did you mean %2", (Type, DeclName, DeclName))
@@ -654,6 +662,9 @@ ERROR(unspaced_unary_operator,none,
ERROR(use_unresolved_identifier,none,
"use of unresolved %select{identifier|operator}1 %0", (DeclName, bool))
ERROR(use_unresolved_identifier_corrected,none,
"use of unresolved %select{identifier|operator}1 %0; did you mean %2?",
(DeclName, bool, DeclName))
NOTE(confusable_character,none,
"%select{identifier|operator}0 '%1' contains possibly confused characters; "
"did you mean to use '%2'?",

View File

@@ -920,12 +920,11 @@ static std::string getScriptFileName(StringRef name, bool isSwiftVersion3) {
return (Twine(name) + langVer + ".json").str();
}
bool ParseMigratorArgs(MigratorOptions &Opts,
const FrontendOptions &FrontendOpts,
const llvm::Triple &Triple,
const bool isSwiftVersion3,
StringRef ResourcePath, const ArgList &Args,
DiagnosticEngine &Diags) {
static bool ParseMigratorArgs(MigratorOptions &Opts,
LangOptions &LangOpts,
const FrontendOptions &FrontendOpts,
StringRef ResourcePath, const ArgList &Args,
DiagnosticEngine &Diags) {
using namespace options;
Opts.KeepObjcVisibility |= Args.hasArg(OPT_migrate_keep_objc_visibility);
@@ -950,9 +949,13 @@ bool ParseMigratorArgs(MigratorOptions &Opts,
if (auto DataPath = Args.getLastArg(OPT_api_diff_data_file)) {
Opts.APIDigesterDataStorePaths.push_back(DataPath->getValue());
} else {
auto &Triple = LangOpts.Target;
bool isSwiftVersion3 = LangOpts.isSwiftVersion3();
bool Supported = true;
llvm::SmallString<128> dataPath(ResourcePath);
llvm::sys::path::append(dataPath, "migrator");
if (Triple.isMacOSX())
llvm::sys::path::append(dataPath,
getScriptFileName("macos", isSwiftVersion3));
@@ -991,6 +994,9 @@ bool ParseMigratorArgs(MigratorOptions &Opts,
// supplementary output for the whole compilation instead of one per input,
// so it's probably not worth it.
FrontendOpts.InputsAndOutputs.assertMustNotBeMoreThanOnePrimaryInput();
// Always disable typo-correction in the migrator.
LangOpts.TypoCorrectionLimit = 0;
}
return false;
@@ -1061,8 +1067,7 @@ bool CompilerInvocation::parseArgs(
return true;
}
if (ParseMigratorArgs(MigratorOpts, FrontendOpts, LangOpts.Target,
LangOpts.isSwiftVersion3(),
if (ParseMigratorArgs(MigratorOpts, LangOpts, FrontendOpts,
SearchPathOpts.RuntimeResourcePath, ParsedArgs, Diags)) {
return true;
}

View File

@@ -18,6 +18,7 @@
#include "CSDiag.h"
#include "CalleeCandidateInfo.h"
#include "MiscDiagnostics.h"
#include "TypoCorrection.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
@@ -1330,25 +1331,20 @@ diagnoseTypeMemberOnInstanceLookup(Type baseObjTy,
/// lower case counterparts are identical.
/// - DeclName is valid when such a correct case is found; invalid otherwise.
static DeclName
findCorrectEnumCaseName(Type Ty, LookupResult &Result,
findCorrectEnumCaseName(Type Ty, TypoCorrectionResults &corrections,
DeclName memberName) {
if (!memberName.isSimpleName())
return DeclName();
if (!Ty->is<EnumType>() &&
!Ty->is<BoundGenericEnumType>())
return DeclName();
llvm::SmallVector<DeclName, 4> candidates;
for (auto &correction : Result) {
DeclName correctName = correction.getValueDecl()->getFullName();
if (!isa<EnumElementDecl>(correction.getValueDecl()))
continue;
if (correctName.getBaseIdentifier().str().equals_lower(
memberName.getBaseIdentifier().str()))
candidates.push_back(correctName.getBaseName());
}
if (candidates.size() == 1)
return candidates.front();
return DeclName();
auto candidate =
corrections.getUniqueCandidateMatching([&](ValueDecl *candidate) {
return (isa<EnumElementDecl>(candidate) &&
candidate->getFullName().getBaseIdentifier().str()
.equals_lower(memberName.getBaseIdentifier().str()));
});
return (candidate ? candidate->getFullName() : DeclName());
}
/// Given a result of name lookup that had no viable results, diagnose the
@@ -1362,12 +1358,10 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
// If we found no results at all, mention that fact.
if (result.UnviableCandidates.empty()) {
LookupResult correctionResults;
TypoCorrectionResults corrections(CS.TC, memberName, nameLoc);
auto tryTypoCorrection = [&] {
CS.TC.performTypoCorrection(CS.DC, DeclRefKind::Ordinary, baseObjTy,
memberName, nameLoc.getBaseNameLoc(),
defaultMemberLookupOptions,
correctionResults);
defaultMemberLookupOptions, corrections);
};
// TODO: This should handle tuple member lookups, like x.1231 as well.
@@ -1382,7 +1376,7 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
tryTypoCorrection();
if (DeclName rightName = findCorrectEnumCaseName(instanceTy,
correctionResults,
corrections,
memberName)) {
diagnose(loc, diag::could_not_find_enum_case, instanceTy,
memberName, rightName)
@@ -1390,8 +1384,17 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
rightName.getBaseIdentifier().str());
return;
}
diagnose(loc, diag::could_not_find_type_member, instanceTy, memberName)
.highlight(baseRange).highlight(nameLoc.getSourceRange());
if (auto correction = corrections.claimUniqueCorrection()) {
auto diagnostic =
diagnose(loc, diag::could_not_find_type_member_corrected,
instanceTy, memberName, correction->CorrectedName);
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
correction->addFixits(diagnostic);
} else {
diagnose(loc, diag::could_not_find_type_member, instanceTy, memberName)
.highlight(baseRange).highlight(nameLoc.getSourceRange());
}
} else if (auto moduleTy = baseObjTy->getAs<ModuleType>()) {
diagnose(baseExpr->getLoc(), diag::no_member_of_module,
moduleTy->getModule()->getName(), memberName)
@@ -1399,31 +1402,43 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
.highlight(nameLoc.getSourceRange());
return;
} else {
diagnose(loc, diag::could_not_find_value_member,
baseObjTy, memberName)
.highlight(baseRange).highlight(nameLoc.getSourceRange());
tryTypoCorrection();
auto emitBasicError = [&] {
diagnose(loc, diag::could_not_find_value_member,
baseObjTy, memberName)
.highlight(baseRange).highlight(nameLoc.getSourceRange());
};
// Check for a few common cases that can cause missing members.
if (baseObjTy->is<EnumType>() && memberName.isSimpleName("rawValue")) {
auto loc = baseObjTy->castTo<EnumType>()->getDecl()->getNameLoc();
if (loc.isValid()) {
emitBasicError();
diagnose(loc, diag::did_you_mean_raw_type);
return; // Always prefer this over typo corrections.
return;
}
} else if (baseObjTy->isAny()) {
emitBasicError();
diagnose(loc, diag::any_as_anyobject_fixit)
.fixItInsert(baseExpr->getStartLoc(), "(")
.fixItInsertAfter(baseExpr->getEndLoc(), " as AnyObject)");
return;
}
tryTypoCorrection();
if (auto correction = corrections.claimUniqueCorrection()) {
auto diagnostic =
diagnose(loc, diag::could_not_find_value_member_corrected,
baseObjTy, memberName, correction->CorrectedName);
diagnostic.highlight(baseRange).highlight(nameLoc.getSourceRange());
correction->addFixits(diagnostic);
} else {
emitBasicError();
}
}
// Note all the correction candidates.
for (auto &correction : correctionResults) {
CS.TC.noteTypoCorrection(memberName, nameLoc,
correction.getValueDecl());
}
corrections.noteAllCandidates();
// TODO: recover?
return;
@@ -6803,11 +6818,13 @@ static bool diagnoseKeyPathComponents(ConstraintSystem &CS, KeyPathExpr *KPE,
// If we didn't find anything, try to apply typo-correction.
bool resultsAreFromTypoCorrection = false;
if (!lookup) {
TypoCorrectionResults corrections(TC, componentName,
DeclNameLoc(componentNameLoc));
TC.performTypoCorrection(CS.DC, DeclRefKind::Ordinary, lookupType,
componentName, componentNameLoc,
(lookupType ? defaultMemberTypeLookupOptions
: defaultUnqualifiedLookupOptions),
lookup);
corrections);
if (currentType)
TC.diagnose(componentNameLoc, diag::could_not_find_type_member,
@@ -6817,10 +6834,8 @@ static bool diagnoseKeyPathComponents(ConstraintSystem &CS, KeyPathExpr *KPE,
componentName, false);
// Note all the correction candidates.
for (auto &result : lookup) {
TC.noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
result.getValueDecl());
}
corrections.noteAllCandidates();
corrections.addAllCandidatesToLookup(lookup);
isInvalid = true;
if (!lookup)

View File

@@ -19,6 +19,7 @@
#include "ConstraintSystem.h"
#include "GenericTypeResolver.h"
#include "TypeChecker.h"
#include "TypoCorrection.h"
#include "MiscDiagnostics.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/ASTWalker.h"
@@ -448,12 +449,6 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
// one, but we should also try to propagate labels into this.
DeclNameLoc nameLoc = UDRE->getNameLoc();
performTypoCorrection(DC, UDRE->getRefKind(), Type(), Name, Loc,
lookupOptions, Lookup);
diagnose(Loc, diag::use_unresolved_identifier, Name, Name.isOperator())
.highlight(UDRE->getSourceRange());
Identifier simpleName = Name.getBaseIdentifier();
const char *buffer = simpleName.get();
llvm::SmallString<64> expectedIdentifier;
@@ -477,16 +472,34 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
offset += length;
}
if (isConfused) {
auto emitBasicError = [&] {
diagnose(Loc, diag::use_unresolved_identifier, Name, Name.isOperator())
.highlight(UDRE->getSourceRange());
};
bool claimed = false;
if (!isConfused) {
TypoCorrectionResults corrections(*this, Name, nameLoc);
performTypoCorrection(DC, UDRE->getRefKind(), Type(),
lookupOptions, corrections);
if (auto typo = corrections.claimUniqueCorrection()) {
auto diag = diagnose(Loc, diag::use_unresolved_identifier_corrected,
Name, Name.isOperator(), typo->CorrectedName);
diag.highlight(UDRE->getSourceRange());
typo->addFixits(diag);
} else {
emitBasicError();
}
corrections.noteAllCandidates();
} else {
emitBasicError();
diagnose(Loc, diag::confusable_character,
UDRE->getName().isOperator(), simpleName.str(),
expectedIdentifier)
.fixItReplace(Loc, expectedIdentifier);
} else {
// Note all the correction candidates.
for (auto &result : Lookup) {
noteTypoCorrection(Name, nameLoc, result.getValueDecl());
}
}
// TODO: consider recovering from here. We may want some way to suppress

View File

@@ -15,6 +15,7 @@
//
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "TypoCorrection.h"
#include "swift/Basic/Range.h"
using namespace swift;
@@ -248,11 +249,12 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
// If we didn't find anything, try to apply typo-correction.
bool resultsAreFromTypoCorrection = false;
if (!lookup) {
TypoCorrectionResults corrections(*this, componentName,
DeclNameLoc(componentNameLoc));
performTypoCorrection(dc, DeclRefKind::Ordinary, lookupType,
componentName, componentNameLoc,
(lookupType ? defaultMemberTypeLookupOptions
: defaultUnqualifiedLookupOptions),
lookup);
corrections);
if (currentType)
diagnose(componentNameLoc, diag::could_not_find_type_member,
@@ -262,10 +264,8 @@ Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
componentName, false);
// Note all the correction candidates.
for (auto &result : lookup) {
noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
result.getValueDecl());
}
corrections.noteAllCandidates();
corrections.addAllCandidatesToLookup(lookup);
isInvalid = true;
if (!lookup) break;

View File

@@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "GenericTypeResolver.h"
#include "TypoCorrection.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/GenericSignatureBuilder.h"
#include "swift/AST/ProtocolConformance.h"
@@ -147,20 +148,18 @@ Type CompleteGenericTypeResolver::resolveDependentMemberType(
} else {
// Resolve the base to a potential archetype.
// Perform typo correction.
LookupResult corrections;
TypoCorrectionResults corrections(tc, ref->getIdentifier(),
DeclNameLoc(ref->getIdLoc()));
tc.performTypoCorrection(DC, DeclRefKind::Ordinary,
MetatypeType::get(baseTy),
ref->getIdentifier(), ref->getIdLoc(),
NameLookupFlags::ProtocolMembers,
corrections, &builder);
// Filter out non-types.
corrections.filter([](const LookupResultEntry &result) {
return isa<TypeDecl>(result.getValueDecl());
});
// Check whether we have a single type result.
auto singleType = corrections.getSingleTypeResult();
auto singleType = cast_or_null<TypeDecl>(
corrections.getUniqueCandidateMatching([](ValueDecl *result) {
return isa<TypeDecl>(result);
}));
// If we don't have a single result, complain and fail.
if (!singleType) {
@@ -168,9 +167,7 @@ Type CompleteGenericTypeResolver::resolveDependentMemberType(
SourceLoc nameLoc = ref->getIdLoc();
tc.diagnose(nameLoc, diag::invalid_member_type, name, baseTy)
.highlight(baseRange);
for (const auto &suggestion : corrections)
tc.noteTypoCorrection(name, DeclNameLoc(nameLoc),
suggestion.getValueDecl());
corrections.noteAllCandidates();
return ErrorType::get(tc.Context);
}

View File

@@ -16,6 +16,7 @@
//
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "TypoCorrection.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/NameLookup.h"
@@ -497,28 +498,30 @@ enum : unsigned {
MaxCallEditDistanceFromBestCandidate = 1
};
static unsigned getCallEditDistance(DeclName argName, DeclName paramName,
static unsigned getCallEditDistance(DeclName writtenName,
DeclName correctedName,
unsigned maxEditDistance) {
// TODO: consider arguments.
// TODO: maybe ignore certain kinds of missing / present labels for the
// first argument label?
// TODO: word-based rather than character-based?
if (argName.getBaseName().getKind() != paramName.getBaseName().getKind()) {
if (writtenName.getBaseName().getKind() !=
correctedName.getBaseName().getKind()) {
return UnreasonableCallEditDistance;
}
if (argName.getBaseName().getKind() != DeclBaseName::Kind::Normal) {
if (writtenName.getBaseName().getKind() != DeclBaseName::Kind::Normal) {
return 0;
}
assert(argName.getBaseName().getKind() == DeclBaseName::Kind::Normal);
StringRef argBase = argName.getBaseName().userFacingName();
StringRef paramBase = paramName.getBaseName().userFacingName();
StringRef writtenBase = writtenName.getBaseName().userFacingName();
StringRef correctedBase = correctedName.getBaseName().userFacingName();
unsigned distance = argBase.edit_distance(paramBase, maxEditDistance);
unsigned distance = writtenBase.edit_distance(correctedBase, maxEditDistance);
// Bound the distance to UnreasonableCallEditDistance.
if (distance >= maxEditDistance || distance > (paramBase.size() + 2) / 3) {
if (distance >= maxEditDistance ||
distance > (correctedBase.size() + 2) / 3) {
return UnreasonableCallEditDistance;
}
@@ -554,10 +557,8 @@ static bool isLocInVarInit(TypeChecker &TC, VarDecl *var, SourceLoc loc) {
void TypeChecker::performTypoCorrection(DeclContext *DC, DeclRefKind refKind,
Type baseTypeOrNull,
DeclName targetDeclName,
SourceLoc nameLoc,
NameLookupOptions lookupOptions,
LookupResult &result,
TypoCorrectionResults &corrections,
GenericSignatureBuilder *gsb,
unsigned maxResults) {
// Disable typo-correction if we won't show the diagnostic anyway or if
@@ -570,27 +571,30 @@ void TypeChecker::performTypoCorrection(DeclContext *DC, DeclRefKind refKind,
++NumTypoCorrections;
// Fill in a collection of the most reasonable entries.
TopCollection<unsigned, ValueDecl*> entries(maxResults);
TopCollection<unsigned, ValueDecl *> entries(maxResults);
auto consumer = makeDeclConsumer([&](ValueDecl *decl,
DeclVisibilityKind reason) {
// Never match an operator with an identifier or vice-versa; this is
// not a plausible typo.
if (!isPlausibleTypo(refKind, targetDeclName, decl))
if (!isPlausibleTypo(refKind, corrections.WrittenName, decl))
return;
// Don't suggest a variable within its own initializer.
if (auto var = dyn_cast<VarDecl>(decl)) {
if (isLocInVarInit(*this, var, nameLoc))
if (isLocInVarInit(*this, var, corrections.Loc.getBaseNameLoc()))
return;
}
auto candidateName = decl->getFullName();
// Don't waste time computing edit distances that are more than
// the worst in our collection.
unsigned maxDistance =
entries.getMinUninterestingScore(UnreasonableCallEditDistance);
unsigned distance =
getCallEditDistance(targetDeclName, decl->getFullName(), maxDistance);
getCallEditDistance(corrections.WrittenName, candidateName,
maxDistance);
// Ignore values that are further than a reasonable distance.
if (distance >= UnreasonableCallEditDistance)
@@ -603,55 +607,125 @@ void TypeChecker::performTypoCorrection(DeclContext *DC, DeclRefKind refKind,
lookupVisibleMemberDecls(consumer, baseTypeOrNull, DC, this,
/*include instance members*/ true, gsb);
} else {
lookupVisibleDecls(consumer, DC, this, /*top level*/ true, nameLoc);
lookupVisibleDecls(consumer, DC, this, /*top level*/ true,
corrections.Loc.getBaseNameLoc());
}
// Impose a maximum distance from the best score.
entries.filterMaxScoreRange(MaxCallEditDistanceFromBestCandidate);
for (auto &entry : entries)
result.add(LookupResultEntry(entry.Value));
corrections.Candidates.push_back(entry.Value);
}
static InFlightDiagnostic
diagnoseTypoCorrection(TypeChecker &tc, DeclNameLoc loc, ValueDecl *decl) {
if (auto var = dyn_cast<VarDecl>(decl)) {
// Suggest 'self' at the use point instead of pointing at the start
// of the function.
if (var->isSelfParameter())
return tc.diagnose(loc.getBaseNameLoc(), diag::note_typo_candidate,
var->getName().str());
}
void
TypoCorrectionResults::addAllCandidatesToLookup(LookupResult &lookup) const {
for (auto candidate : Candidates)
lookup.add(LookupResultEntry(candidate));
}
static Decl *findExplicitParentForImplicitDecl(ValueDecl *decl) {
if (!decl->getLoc().isValid() && decl->getDeclContext()->isTypeContext()) {
Decl *parentDecl = dyn_cast<ExtensionDecl>(decl->getDeclContext());
if (!parentDecl) parentDecl = cast<NominalTypeDecl>(decl->getDeclContext());
if (parentDecl->getLoc().isValid())
return parentDecl;
}
if (parentDecl->getLoc().isValid()) {
StringRef kind = (isa<VarDecl>(decl) ? "property" :
isa<ConstructorDecl>(decl) ? "initializer" :
isa<FuncDecl>(decl) ? "method" :
"member");
return nullptr;
}
return tc.diagnose(parentDecl, diag::note_typo_candidate_implicit_member,
decl->getBaseName().userFacingName(), kind);
static InFlightDiagnostic
noteTypoCorrection(TypeChecker &tc, DeclNameLoc loc, ValueDecl *decl,
bool wasClaimed) {
if (auto var = dyn_cast<VarDecl>(decl)) {
// Suggest 'self' at the use point instead of pointing at the start
// of the function.
if (var->isSelfParameter()) {
if (wasClaimed) {
// We don't need an extra note for this case because the programmer
// knows what 'self' refers to.
return InFlightDiagnostic();
}
return tc.diagnose(loc.getBaseNameLoc(), diag::note_typo_candidate,
var->getName().str());
}
}
return tc.diagnose(decl, diag::note_typo_candidate,
decl->getBaseName().userFacingName());
if (Decl *parentDecl = findExplicitParentForImplicitDecl(decl)) {
StringRef kind = (isa<VarDecl>(decl) ? "property" :
isa<ConstructorDecl>(decl) ? "initializer" :
isa<FuncDecl>(decl) ? "method" :
"member");
return tc.diagnose(parentDecl,
wasClaimed ? diag::implicit_member_declared_here
: diag::note_typo_candidate_implicit_member,
decl->getBaseName().userFacingName(), kind);
}
if (wasClaimed) {
return tc.diagnose(decl, diag::decl_declared_here, decl->getBaseName());
} else {
return tc.diagnose(decl, diag::note_typo_candidate,
decl->getBaseName().userFacingName());
}
}
void TypeChecker::noteTypoCorrection(DeclName writtenName, DeclNameLoc loc,
ValueDecl *decl) {
auto &&diagnostic = diagnoseTypoCorrection(*this, loc, decl);
void TypoCorrectionResults::noteAllCandidates() const {
for (auto candidate : Candidates) {
auto &&diagnostic =
noteTypoCorrection(TC, Loc, candidate, ClaimedCorrection);
DeclName declName = decl->getFullName();
// Don't add fix-its if we claimed the correction for the primary
// diagnostic.
if (!ClaimedCorrection) {
SyntacticTypoCorrection correction(WrittenName, Loc,
candidate->getFullName());
correction.addFixits(diagnostic);
}
}
}
if (writtenName.getBaseName() != declName.getBaseName())
diagnostic.fixItReplace(loc.getBaseNameLoc(),
declName.getBaseName().userFacingName());
void SyntacticTypoCorrection::addFixits(InFlightDiagnostic &diagnostic) const {
if (WrittenName.getBaseName() != CorrectedName.getBaseName())
diagnostic.fixItReplace(Loc.getBaseNameLoc(),
CorrectedName.getBaseName().userFacingName());
// TODO: add fix-its for typo'ed argument labels. This is trickier
// because of the reordering rules.
}
Optional<SyntacticTypoCorrection>
TypoCorrectionResults::claimUniqueCorrection() {
// Look for a unique base name. We ignore the rest of the name for now
// because we don't actually typo-correct any of that.
DeclBaseName uniqueCorrectedName;
for (auto candidate : Candidates) {
auto candidateName = candidate->getBaseName();
// If this is the first name, record it.
if (uniqueCorrectedName.empty())
uniqueCorrectedName = candidateName;
// If this is a different name from the last candidate, we don't have
// a unique correction.
else if (uniqueCorrectedName != candidateName)
return None;
}
// If we didn't find any candidates, we're done.
if (uniqueCorrectedName.empty())
return None;
// If the corrected name doesn't differ from the written name in its base
// name, it's not simple enough for this (for now).
if (WrittenName.getBaseName() == uniqueCorrectedName)
return None;
// Flag that we've claimed the correction.
ClaimedCorrection = true;
return SyntacticTypoCorrection(WrittenName, Loc, uniqueCorrectedName);
}

View File

@@ -42,6 +42,7 @@ class NominalTypeDecl;
class NormalProtocolConformance;
class TopLevelContext;
class TypeChecker;
class TypoCorrectionResults;
class ExprPattern;
namespace constraints {
@@ -2494,16 +2495,11 @@ public:
void performTypoCorrection(DeclContext *DC,
DeclRefKind refKind,
Type baseTypeOrNull,
DeclName name,
SourceLoc lookupLoc,
NameLookupOptions lookupOptions,
LookupResult &result,
TypoCorrectionResults &corrections,
GenericSignatureBuilder *gsb = nullptr,
unsigned maxResults = 4);
void noteTypoCorrection(DeclName name, DeclNameLoc nameLoc,
ValueDecl *decl);
/// Check if the given decl has a @_semantics attribute that gives it
/// special case type-checking behavior.
DeclTypeCheckingSemantics getDeclTypeCheckingSemantics(ValueDecl *decl);

103
lib/Sema/TypoCorrection.h Normal file
View File

@@ -0,0 +1,103 @@
//===--- TypoCorrection.h - Typo correction ---------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines the interface for doing typo correction.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SEMA_TYPOCORRECTION_H
#define SWIFT_SEMA_TYPOCORRECTION_H
#include "swift/Basic/LLVM.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/DeclNameLoc.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/STLExtras.h"
namespace swift {
class LookupResult;
class TypeChecker;
/// A summary of how to fix a typo. Note that this intentionally doesn't
/// carry a candidate declaration because we should be able to apply a typo
/// correction even if the corrected name resolves to an overload set.
class SyntacticTypoCorrection {
public:
DeclName WrittenName;
DeclNameLoc Loc;
DeclName CorrectedName;
SyntacticTypoCorrection(DeclName writtenName, DeclNameLoc writtenLoc,
DeclName correctedName)
: WrittenName(writtenName), Loc(writtenLoc), CorrectedName(correctedName) {}
void addFixits(InFlightDiagnostic &diagnostic) const;
};
/// A collection of typo-correction candidates.
class TypoCorrectionResults {
public:
TypeChecker &TC;
DeclName WrittenName;
DeclNameLoc Loc;
bool ClaimedCorrection = false;
SmallVector<ValueDecl *, 4> Candidates;
TypoCorrectionResults(TypeChecker &tc, DeclName writtenName, DeclNameLoc loc)
: TC(tc), WrittenName(writtenName), Loc(loc) {}
/// Try to claim a unique correction from this collection that's simple
/// enough to include "inline" in the primary diagnostic. Note that
/// a single correction might still correspond to multiple candidates.
///
/// The expected pattern is that CorrectedName will be added to the
/// diagnostic (as in "did you mean <<CorrectedName>>"), and then addFixits
/// will be called on the still-in-flight diagnostic.
///
/// If this returns a correction, it flags that that's been done, and
/// the notes subsequently emitted by noteAllCandidates will only make
/// sense in the context of a diagnostic that suggests that the correction
/// has happened.
Optional<SyntacticTypoCorrection> claimUniqueCorrection();
/// Emit a note for every candidate in the set.
void noteAllCandidates() const;
/// Add all the candidates to a lookup set.
void addAllCandidatesToLookup(LookupResult &lookup) const;
/// Look for a single candidate in this set that matches the given predicate.
template <class Fn>
ValueDecl *getUniqueCandidateMatching(Fn &&predicate) {
ValueDecl *match = nullptr;
for (auto candidate : Candidates) {
// Ignore candidates that don't match the predicate.
if (!predicate(candidate)) continue;
// If we've already got a match, the match is no longer unique.
if (match) return nullptr;
// Record the still-unique match.
match = candidate;
}
return match;
}
};
} // end namespace swift
#endif

View File

@@ -13,9 +13,9 @@ func test() {
// Enumerator remapping.
var excuse: HomeworkExcuse = .dogAteIt
excuse = .overslept // FIXME: should provide Fix-It expected-error{{type 'HomeworkExcuse' has no member 'overslept'}} {{none}}
excuse = .overslept // expected-error{{type 'HomeworkExcuse' has no member 'overslept'; did you mean 'Overslept'?}} {{13-22=Overslept}}
excuse = .tired
excuse = .tooHard // FIXME: should provide Fix-It expected-error{{type 'HomeworkExcuse' has no member 'tooHard'}} {{none}}
excuse = .tooHard // expected-error{{type 'HomeworkExcuse' has no member 'tooHard'; did you mean 'TooHard'?}} {{13-20=TooHard}}
excuse = .challenging
// Typedef-of-anonymous-type-name renaming

View File

@@ -433,8 +433,7 @@ struct Aardvark {
func rdar33914444() {
struct A {
enum R<E: Error> {
case e(E)
// expected-note@-1 {{did you mean 'e'}}
case e(E) // expected-note {{'e' declared here}}
}
struct S {
@@ -447,7 +446,7 @@ func rdar33914444() {
}
_ = A.S(e: .e1)
// expected-error@-1 {{type 'A.R<A.S.E>' has no member 'e1'}}
// expected-error@-1 {{type 'A.R<A.S.E>' has no member 'e1'; did you mean 'e'?}}
}
// SR-5324: Better diagnostic when instance member of outer type is referenced from nested type

View File

@@ -200,8 +200,8 @@ class ThisDerived1 : ThisBase1 {
super.derivedExtProp = 42 // expected-error {{value of type 'ThisBase1' has no member 'derivedExtProp'}}
super.derivedExtFunc0() // expected-error {{value of type 'ThisBase1' has no member 'derivedExtFunc0'}}
super.derivedExtStaticVar = 42 // expected-error {{value of type 'ThisBase1' has no member 'derivedExtStaticVar'}}
super.derivedExtStaticProp = 42 // expected-error {{value of type 'ThisBase1' has no member 'derivedExtStaticProp'}}
super.derivedExtStaticVar = 42 // expected-error {{value of type 'ThisBase1' has no member 'derivedExtStaticVar'; did you mean 'baseExtStaticVar'?}}
super.derivedExtStaticProp = 42 // expected-error {{value of type 'ThisBase1' has no member 'derivedExtStaticProp'; did you mean 'baseExtStaticProp'?}}
super.derivedExtStaticFunc0() // expected-error {{value of type 'ThisBase1' has no member 'derivedExtStaticFunc0'}}
var ds2 = super.DerivedNestedStruct() // expected-error {{value of type 'ThisBase1' has no member 'DerivedNestedStruct'}}
@@ -321,9 +321,9 @@ class ThisDerived1 : ThisBase1 {
super.derivedExtProp = 42 // expected-error {{type 'ThisBase1' has no member 'derivedExtProp'}}
super.derivedExtFunc0() // expected-error {{type 'ThisBase1' has no member 'derivedExtFunc0'}}
super.derivedExtStaticVar = 42 // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticVar'}}
super.derivedExtStaticProp = 42 // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticProp'}}
super.derivedExtStaticFunc0() // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticFunc0'}}
super.derivedExtStaticVar = 42 // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticVar'; did you mean 'baseExtStaticVar'?}}
super.derivedExtStaticProp = 42 // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticProp'; did you mean 'baseExtStaticProp'?}}
super.derivedExtStaticFunc0() // expected-error {{type 'ThisBase1' has no member 'derivedExtStaticFunc0'; did you mean 'baseExtStaticFunc0'?}}
var ds2 = super.DerivedNestedStruct() // expected-error {{type 'ThisBase1' has no member 'DerivedNestedStruct'}}
var dc2 = super.DerivedNestedClass() // expected-error {{type 'ThisBase1' has no member 'DerivedNestedClass'}}
@@ -331,11 +331,11 @@ class ThisDerived1 : ThisBase1 {
var do3 = super.DerivedNestedUnion.DerivedUnionX(24) // expected-error {{type 'ThisBase1' has no member 'DerivedNestedUnion'}}
var dt2 = super.DerivedNestedTypealias(42) // expected-error {{type 'ThisBase1' has no member 'DerivedNestedTypealias'}}
var des2 = super.DerivedExtNestedStruct() // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedStruct'}}
var dec2 = super.DerivedExtNestedClass() // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedClass'}}
var des2 = super.DerivedExtNestedStruct() // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedStruct'; did you mean 'BaseExtNestedStruct'?}}
var dec2 = super.DerivedExtNestedClass() // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedClass'; did you mean 'BaseExtNestedClass'?}}
var deo2 = super.DerivedExtUnionX(24) // expected-error {{type 'ThisBase1' has no member 'DerivedExtUnionX'}}
var deo3 = super.DerivedExtNestedUnion.DerivedExtUnionX(24) // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedUnion'}}
var det2 = super.DerivedExtNestedTypealias(42) // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedTypealias'}}
var deo3 = super.DerivedExtNestedUnion.DerivedExtUnionX(24) // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedUnion'; did you mean 'BaseExtNestedUnion'?}}
var det2 = super.DerivedExtNestedTypealias(42) // expected-error {{type 'ThisBase1' has no member 'DerivedExtNestedTypealias'; did you mean 'BaseExtNestedTypealias'?}}
super.Type // expected-error {{type 'ThisBase1' has no member 'Type'}}
}
@@ -351,26 +351,26 @@ extension ThisBase1 {
func baseExtFunc0() {}
var baseExtStaticVar: Int // expected-error {{extensions must not contain stored properties}} // expected-note 2 {{did you mean 'baseExtStaticVar'?}}
var baseExtStaticVar: Int // expected-error {{extensions must not contain stored properties}} // expected-note 2 {{'baseExtStaticVar' declared here}}
var baseExtStaticProp: Int { // expected-note 2 {{did you mean 'baseExtStaticProp'?}}
var baseExtStaticProp: Int { // expected-note 2 {{'baseExtStaticProp' declared here}}
get {
return 42
}
set {}
}
class func baseExtStaticFunc0() {} // expected-note {{did you mean 'baseExtStaticFunc0'?}}
class func baseExtStaticFunc0() {} // expected-note {{'baseExtStaticFunc0' declared here}}
struct BaseExtNestedStruct {} // expected-note 2 {{did you mean 'BaseExtNestedStruct'?}}
class BaseExtNestedClass { // expected-note {{did you mean 'BaseExtNestedClass'?}}
struct BaseExtNestedStruct {} // expected-note {{did you mean 'BaseExtNestedStruct'?}} // expected-note {{'BaseExtNestedStruct' declared here}}
class BaseExtNestedClass { // expected-note {{'BaseExtNestedClass' declared here}}
init() { }
}
enum BaseExtNestedUnion { // expected-note {{did you mean 'BaseExtNestedUnion'?}}
enum BaseExtNestedUnion { // expected-note {{'BaseExtNestedUnion' declared here}}
case BaseExtUnionX(Int)
}
typealias BaseExtNestedTypealias = Int // expected-note 2 {{did you mean 'BaseExtNestedTypealias'?}}
typealias BaseExtNestedTypealias = Int // expected-note {{did you mean 'BaseExtNestedTypealias'?}} // expected-note {{'BaseExtNestedTypealias' declared here}}
}
extension ThisDerived1 {
@@ -577,13 +577,13 @@ struct Person {
let name: String?
}
struct Company { // expected-note 2{{did you mean 'Company'?}}
struct Company { // expected-note 2{{'Company' declared here}}
let owner: Person?
}
func test1() {
let example: Company? = Company(owner: Person(name: "Owner"))
if let person = aCompany.owner, // expected-error {{use of unresolved identifier 'aCompany'}}
if let person = aCompany.owner, // expected-error {{use of unresolved identifier 'aCompany'; did you mean 'Company'?}}
let aCompany = example {
_ = person
}
@@ -591,13 +591,13 @@ func test1() {
func test2() {
let example: Company? = Company(owner: Person(name: "Owner"))
guard let person = aCompany.owner, // expected-error {{use of unresolved identifier 'aCompany'}}
guard let person = aCompany.owner, // expected-error {{use of unresolved identifier 'aCompany'; did you mean 'Company'?}}
let aCompany = example else { return }
}
func test3() {
var c: String? = "c" // expected-note {{did you mean 'c'?}}
if let a = b = c, let b = c { // expected-error {{use of unresolved identifier 'b'}}
var c: String? = "c" // expected-note {{'c' declared here}}
if let a = b = c, let b = c { // expected-error {{use of unresolved identifier 'b'; did you mean 'c'?}}
_ = b
}
}

View File

@@ -8,7 +8,7 @@ struct IntRange<Int> : Sequence, IteratorProtocol {
func makeIterator() -> IntRange<Int> { return self }
}
func for_each(r: Range<Int>, iir: IntRange<Int>) { // expected-note {{did you mean 'r'?}}
func for_each(r: Range<Int>, iir: IntRange<Int>) { // expected-note {{'r' declared here}}
var sum = 0
// Simple foreach loop, using the variable in the body
@@ -16,7 +16,7 @@ func for_each(r: Range<Int>, iir: IntRange<Int>) { // expected-note {{did you me
sum = sum + i
}
// Check scoping of variable introduced with foreach loop
i = 0 // expected-error{{use of unresolved identifier 'i'}}
i = 0 // expected-error{{use of unresolved identifier 'i'; did you mean 'r'?}}
// For-each loops with two variables and varying degrees of typedness
for (i, j) in iir {

View File

@@ -21,11 +21,11 @@ func test3() {
undeclared_func( // expected-error {{use of unresolved identifier 'undeclared_func'}}
} // expected-error {{expected expression in list of expressions}}
func runAction() {} // expected-note {{did you mean 'runAction'?}}
func runAction() {} // expected-note {{'runAction' declared here}}
// rdar://16601779
func foo() {
runAction(SKAction.sequence() // expected-error {{use of unresolved identifier 'SKAction'}} expected-error {{expected ',' separator}} {{32-32=,}}
runAction(SKAction.sequence() // expected-error {{use of unresolved identifier 'SKAction'; did you mean 'runAction'?}} {{13-21=runAction}} expected-error {{expected ',' separator}} {{32-32=,}}
skview!
// expected-error @-1 {{use of unresolved identifier 'skview'}}

View File

@@ -316,8 +316,8 @@ func enumElementSyntaxOnTuple() {
// sr-176
enum Whatever { case Thing }
func f0(values: [Whatever]) { // expected-note {{did you mean 'values'?}}
switch value { // expected-error {{use of unresolved identifier 'value'}}
func f0(values: [Whatever]) { // expected-note {{'values' declared here}}
switch value { // expected-error {{use of unresolved identifier 'value'; did you mean 'values'?}}
case .Thing: // Ok. Don't emit diagnostics about enum case not found in type <<error type>>.
break
}

View File

@@ -2,14 +2,14 @@
enum E : String {
case foo = "foo"
case bar = "bar" // expected-note {{did you mean 'bar'?}}
case bar = "bar" // expected-note {{'bar' declared here}}
}
func fe(_: E) {}
func fe(_: Int) {}
func fe(_: Int, _: E) {}
func fe(_: Int, _: Int) {}
fe(E.baz) // expected-error {{type 'E' has no member 'baz'}}
fe(E.baz) // expected-error {{type 'E' has no member 'baz'; did you mean 'bar'?}}
fe(.baz) // expected-error {{reference to member 'baz' cannot be resolved without a contextual type}}
// FIXME: maybe complain about .nope also?

View File

@@ -2,13 +2,13 @@
enum Key: Int {
case aKey
case anotherKey // expected-note {{did you mean 'anotherKey'?}}
case anotherKey // expected-note {{'anotherKey' declared here}}
}
class sr6175 {
var dict: [Key: String] = [:]
func what() -> Void {
dict[.notAKey] = "something" // expected-error {{type 'Key' has no member 'notAKey'}}
dict[.notAKey] = "something" // expected-error {{type 'Key' has no member 'notAKey'; did you mean 'anotherKey'?}}
}
subscript(i: Int) -> Int {

View File

@@ -106,7 +106,7 @@ func rdar32431736() {
enum E_32431165 : String {
case foo = "foo"
case bar = "bar" // expected-note {{did you mean 'bar'?}}
case bar = "bar" // expected-note {{'bar' declared here}}
}
func rdar32431165_1(_: E_32431165) {}
@@ -114,7 +114,7 @@ func rdar32431165_1(_: Int) {}
func rdar32431165_1(_: Int, _: E_32431165) {}
rdar32431165_1(E_32431165.baz)
// expected-error@-1 {{type 'E_32431165' has no member 'baz'}}
// expected-error@-1 {{type 'E_32431165' has no member 'baz'; did you mean 'bar'?}}
rdar32431165_1(.baz)
// expected-error@-1 {{reference to member 'baz' cannot be resolved without a contextual type}}

View File

@@ -10,8 +10,9 @@ import NoSuchModule
// This is close enough to get typo-correction.
func test_short_and_close() {
let foo = 4 // expected-note {{did you mean 'foo'?}}
let bab = fob + 1 // expected-error {{use of unresolved identifier}}
let foo = 4 // expected-note {{'foo' declared here}}
let bab = fob + 1
// expected-error@-1 {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
}
// This is not.
@@ -26,8 +27,9 @@ func *(x: Whatever, y: Whatever) {}
// This works even for single-character identifiers.
func test_very_short() {
// Note that we don't suggest operators.
let x = 0 // expected-note {{did you mean 'x'?}}
let longer = y // expected-error {{use of unresolved identifier 'y'}}
let x = 0 // expected-note {{'x' declared here}}
let longer = y
// expected-error@-1 {{use of unresolved identifier 'y'; did you mean 'x'?}}
}
// It does not trigger in a variable's own initializer.
@@ -51,9 +53,10 @@ func test_keep_if_not_too_much_worse() {
// Report not-as-good matches if they're still close enough to the best.
func test_drop_if_too_different() {
let longlongmatch1 = 0 // expected-note {{did you mean 'longlongmatch1'?}}
let longlongmatch1 = 0 // expected-note {{'longlongmatch1' declared here}}
let longlongmatch2222 = 0
let x = longlongmatch // expected-error {{use of unresolved identifier 'longlongmatch'}}
let x = longlongmatch
// expected-error@-1 {{use of unresolved identifier 'longlongmatch'; did you mean 'longlongmatch1'?}}
}
// Candidates are suppressed if we have too many that are the same distance.
@@ -88,24 +91,23 @@ _ = [Any]().withUnsafeBufferPointer { (buf) -> [Any] in
// Typo correction with class-bound archetypes.
class SomeClass {
func match1() {}
// expected-note@-1 {{did you mean 'match1'?}}
func match1() {} // expected-note {{'match1' declared here}}
}
func takesSomeClassArchetype<T : SomeClass>(_ t: T) {
t.match0()
// expected-error@-1 {{value of type 'T' has no member 'match0'}}
// expected-error@-1 {{value of type 'T' has no member 'match0'; did you mean 'match1'?}}{{5-11=match1}}
}
// Typo correction of unqualified lookup from generic context.
struct Generic<T> {
func match1() {}
// expected-note@-1 {{did you mean 'match1'?}}
// expected-note@-1 {{'match1' declared here}}
class Inner {
func doStuff() {
match0()
// expected-error@-1 {{use of unresolved identifier 'match0'}}
// expected-error@-1 {{use of unresolved identifier 'match0'; did you mean 'match1'?}}
}
}
}
@@ -123,15 +125,22 @@ func takesAnyObjectArchetype<T : AnyObject>(_ t: T) {
// Typo correction with an UnresolvedDotExpr.
enum Foo {
// note: the fixit is actually for the line with the error below, but
// -verify mode is not smart enough for that yet.
case flashing // expected-note {{did you mean 'flashing'?}}{{8-15=flashing}}
case flashing // expected-note {{'flashing' declared here}}
}
func foo(_ a: Foo) {
}
func bar() {
foo(.flashin) // expected-error {{type 'Foo' has no member 'flashin'}}
foo(.flashin)
// expected-error@-1 {{type 'Foo' has no member 'flashin'; did you mean 'flashing'?}}{{8-15=flashing}}
}
// Verify that we emit a fixit even if there are multiple
// declarations with the corrected name.
func overloaded(_: Int) {} // expected-note {{'overloaded' declared here}}
func overloaded(_: Float) {} // expected-note {{'overloaded' declared here}}
func test_overloaded() {
overloadd(0)
// expected-error@-1 {{use of unresolved identifier 'overloadd'; did you mean 'overloaded'?}}{{3-12=overloaded}}
}

View File

@@ -2,11 +2,11 @@
// This is close enough to get typo-correction.
func test_short_and_close() {
let foo = 4 // expected-note 5 {{did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let _ = fob + 1 // expected-error {{use of unresolved identifier}}
let foo = 4 // expected-note 5 {{'foo' declared here}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'; did you mean 'foo'?}}
let _ = fob + 1 // expected-error {{use of unresolved identifier 'fob'}}
}

View File

@@ -1,5 +1,5 @@
func use(_ x: Int) {}
func test() {
use(a!)
use(b) // expected-error {{use of unresolved identifier 'b'}}
use(b) // expected-error {{use of unresolved identifier 'b'; did you mean 'a'?}}
}

View File

@@ -7,7 +7,7 @@
// CHECK-NOT: UnknownCode
let a: Int? = 1 // expected-note {{did you mean 'a'?}}
let a: Int? = 1 // expected-note {{'a' declared here}}
guard let b = a else {
fatalError()
}

View File

@@ -12,7 +12,7 @@
key.column: 16,
key.filepath: real.swift,
key.severity: source.diagnostic.severity.error,
key.description: "use of unresolved identifier 'goo'",
key.description: "use of unresolved identifier 'goo'; did you mean 'Bool'?",
key.diagnostic_stage: source.diagnostic.stage.swift.sema,
key.ranges: [
{
@@ -20,13 +20,20 @@
key.length: 3
}
],
key.fixits: [
{
key.offset: 15,
key.length: 3,
key.sourcetext: "Bool"
}
],
key.diagnostics: [
{
key.line: 1,
key.column: 16,
key.filepath: real.swift,
key.severity: source.diagnostic.severity.note,
key.description: "did you mean 'Bool'? (Swift.Bool)"
key.description: "'Bool' declared here (Swift.Bool)"
}
]
}

View File

@@ -41,7 +41,7 @@ func test1b(_ b : Bool) {
enum MaybeInt {
case none
case some(Int) // expected-note {{did you mean 'some'?}}
case some(Int) // expected-note {{'some' declared here}}
init(_ i: Int) { self = MaybeInt.some(i) }
}
@@ -87,7 +87,7 @@ func test3(_ a: ZeroOneTwoThree) {
var _ : (Int,Int) -> ZeroOneTwoThree = .Two // expected-error{{type '(Int, Int) -> ZeroOneTwoThree' has no member 'Two'}}
var _ : Int = .Two // expected-error{{type 'Int' has no member 'Two'}}
var _ : MaybeInt = 0 > 3 ? .none : .soma(3) // expected-error {{type 'MaybeInt' has no member 'soma'}}
var _ : MaybeInt = 0 > 3 ? .none : .soma(3) // expected-error {{type 'MaybeInt' has no member 'soma'; did you mean 'some'?}}
}
func test3a(_ a: ZeroOneTwoThree) {
@@ -310,12 +310,12 @@ enum E21269142 { // expected-note {{did you mean to specify a raw type on the e
print(E21269142.Foo.rawValue) // expected-error {{value of type 'E21269142' has no member 'rawValue'}}
// Check that typo correction does something sensible with synthesized members.
enum SyntheticMember { // expected-note {{did you mean the implicitly-synthesized property 'hashValue'?}}
enum SyntheticMember { // expected-note {{property 'hashValue' is implicitly declared}}
case Foo
}
func useSynthesizedMember() {
print(SyntheticMember.Foo.hasValue) // expected-error {{value of type 'SyntheticMember' has no member 'hasValue'}}
print(SyntheticMember.Foo.hasValue) // expected-error {{value of type 'SyntheticMember' has no member 'hasValue'; did you mean 'hashValue'?}}
}
// Non-materializable argument type

View File

@@ -3,7 +3,7 @@
// REQUIRES: objc_interop
@objc protocol OP1 {
func reqOP1a() -> Bool // expected-note {{did you mean 'reqOP1a'?}}
func reqOP1a() -> Bool // expected-note {{'reqOP1a' declared here}}
}
extension OP1 {
@@ -20,5 +20,5 @@ func testOP1(_ oc1: OC1, ao: AnyObject) {
ao.reqOP1a!() // okay
// Extension of @objc protocol does not have @objc members.
ao.extOP1a!() // expected-error{{value of type 'AnyObject' has no member 'extOP1a'}}
ao.extOP1a!() // expected-error{{value of type 'AnyObject' has no member 'extOP1a'; did you mean 'reqOP1a'?}}
}

View File

@@ -89,7 +89,7 @@ class C1 {
if b { return self.init(int: 5) }
return Self() // expected-error{{use of unresolved identifier 'Self'}} expected-note {{did you mean 'self'?}}
return Self() // expected-error{{use of unresolved identifier 'Self'; did you mean 'self'?}}
}
// This used to crash because metatype construction went down a

View File

@@ -54,8 +54,8 @@ class ClassKey : CodingKey { //expected-error {{type 'ClassKey' does not conform
// Types which are valid for CodingKey derived conformance should not get that
// derivation unless they explicitly conform to CodingKey.
enum X { case a }
enum Y : String { case a } // expected-note {{did you mean the implicitly-synthesized property 'rawValue'?}}
enum Z : Int { case a } // expected-note {{did you mean the implicitly-synthesized property 'rawValue'?}}
enum Y : String { case a } // expected-note {{property 'rawValue' is implicitly declared}}
enum Z : Int { case a } // expected-note {{property 'rawValue' is implicitly declared}}
let _ = X.a.stringValue // expected-error {{value of type 'X' has no member 'stringValue'}}
let _ = Y.a.stringValue // expected-error {{value of type 'Y' has no member 'stringValue'}}
@@ -66,8 +66,8 @@ let _ = Y(stringValue: "a") // expected-error {{incorrect argument label in call
let _ = Z(stringValue: "a") // expected-error {{incorrect argument label in call (have 'stringValue:', expected 'rawValue:')}}
let _ = X.a.intValue // expected-error {{value of type 'X' has no member 'intValue'}}
let _ = Y.a.intValue // expected-error {{value of type 'Y' has no member 'intValue'}}
let _ = Z.a.intValue // expected-error {{value of type 'Z' has no member 'intValue'}}
let _ = Y.a.intValue // expected-error {{value of type 'Y' has no member 'intValue'; did you mean 'rawValue'?}}
let _ = Z.a.intValue // expected-error {{value of type 'Z' has no member 'intValue'; did you mean 'rawValue'?}}
let _ = X(intValue: 0) // expected-error {{'X' cannot be constructed because it has no accessible initializers}}
let _ = Y(intValue: 0) // expected-error {{incorrect argument label in call (have 'intValue:', expected 'rawValue:')}}