mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
QoI: Typo correction for mistyped associated types.
Swift SVN r22678
This commit is contained in:
@@ -264,6 +264,12 @@ public:
|
||||
/// \returns true if an error occurred, false otherwise.
|
||||
bool inferRequirements(Pattern *pattern);
|
||||
|
||||
/// Finalize the set of requirements, performing any remaining checking
|
||||
/// required before generating archetypes.
|
||||
///
|
||||
/// \returns true if an error occurs, false otherwse.
|
||||
bool finalize(SourceLoc loc);
|
||||
|
||||
/// \brief Resolve the given type to the potential archetype it names.
|
||||
///
|
||||
/// This routine will synthesize nested types as required to refer to a
|
||||
@@ -384,6 +390,10 @@ class ArchetypeBuilder::PotentialArchetype {
|
||||
/// \brief Recursively conforms to itself.
|
||||
unsigned IsRecursive : 1;
|
||||
|
||||
/// Whether this potential archetype is invalid, e.g., because it could not
|
||||
/// be resolved.
|
||||
unsigned Invalid : 1;
|
||||
|
||||
/// The references to this nested type that occur in the source code
|
||||
/// that were unresolved (at least at some point).
|
||||
llvm::TinyPtrVector<ComponentIdentTypeRepr *> UnresolvedReferences;
|
||||
@@ -392,7 +402,7 @@ class ArchetypeBuilder::PotentialArchetype {
|
||||
/// associated type.
|
||||
PotentialArchetype(PotentialArchetype *Parent, Identifier Name)
|
||||
: ParentOrParam(Parent), NameOrAssociatedType(Name), Representative(this),
|
||||
IsRecursive(false)
|
||||
IsRecursive(false), Invalid(false)
|
||||
{
|
||||
assert(Parent != nullptr && "Not an associated type?");
|
||||
}
|
||||
@@ -400,7 +410,7 @@ class ArchetypeBuilder::PotentialArchetype {
|
||||
/// \brief Construct a new potential archetype for an associated type.
|
||||
PotentialArchetype(PotentialArchetype *Parent, AssociatedTypeDecl *AssocType)
|
||||
: ParentOrParam(Parent), NameOrAssociatedType(AssocType),
|
||||
Representative(this), IsRecursive(false)
|
||||
Representative(this), IsRecursive(false), Invalid(false)
|
||||
{
|
||||
assert(Parent != nullptr && "Not an associated type?");
|
||||
}
|
||||
@@ -410,7 +420,8 @@ class ArchetypeBuilder::PotentialArchetype {
|
||||
ProtocolDecl *RootProtocol,
|
||||
Identifier Name)
|
||||
: ParentOrParam(GenericParam), RootProtocol(RootProtocol),
|
||||
NameOrAssociatedType(Name), Representative(this), IsRecursive(false) { }
|
||||
NameOrAssociatedType(Name), Representative(this), IsRecursive(false),
|
||||
Invalid(false) { }
|
||||
|
||||
/// \brief Recursively build the full name.
|
||||
void buildFullName(bool forDebug, SmallVectorImpl<char> &result) const;
|
||||
@@ -522,9 +533,13 @@ public:
|
||||
return getGenericParam() && !isConcreteType();
|
||||
}
|
||||
|
||||
void setIsRecursive() { this->IsRecursive = true; }
|
||||
bool isRecursive() { return this->IsRecursive; }
|
||||
|
||||
void setIsRecursive() { IsRecursive = true; }
|
||||
bool isRecursive() { return IsRecursive; }
|
||||
|
||||
bool isInvalid() { return Invalid; }
|
||||
|
||||
void setInvalid() { Invalid = true; }
|
||||
|
||||
/// Return the list of unresolved references to this associated type.
|
||||
ArrayRef<ComponentIdentTypeRepr *> getUnresolvedReferences() const {
|
||||
return UnresolvedReferences;
|
||||
|
||||
@@ -127,6 +127,9 @@ ERROR(ambiguous_type_base,sema_nb,none,
|
||||
"%0 is ambiguous for type lookup in this context", (Identifier))
|
||||
ERROR(invalid_member_type,sema_nb,none,
|
||||
"%0 is not a member type of %1", (Identifier, Type))
|
||||
ERROR(invalid_member_type_suggest,sema_nb,none,
|
||||
"%0 does not have a member type named %1; did you mean %2?",
|
||||
(Type, Identifier, Identifier))
|
||||
ERROR(ambiguous_member_type,sema_nb,none,
|
||||
"ambiguous type name %0 in %1", (Identifier, Type))
|
||||
ERROR(no_module_type,sema_nb,none,
|
||||
|
||||
@@ -313,8 +313,6 @@ bool ArchetypeBuilder::PotentialArchetype::addConformance(
|
||||
|
||||
// Check whether any associated types in this protocol resolve
|
||||
// nested types of this potential archetype.
|
||||
// FIXME: Eventually, will need to add more same-type requirements
|
||||
// here as well.
|
||||
for (auto member : proto->getMembers()) {
|
||||
auto assocType = dyn_cast<AssociatedTypeDecl>(member);
|
||||
if (!assocType)
|
||||
@@ -791,7 +789,7 @@ bool ArchetypeBuilder::addSameTypeRequirementBetweenArchetypes(
|
||||
// associated type.
|
||||
unsigned T1Depth = T1->getNestingDepth();
|
||||
unsigned T2Depth = T2->getNestingDepth();
|
||||
if (T2Depth < T1Depth)
|
||||
if (T2Depth < T1Depth || (T1->isInvalid() && !T2->isInvalid()))
|
||||
std::swap(T1, T2);
|
||||
|
||||
// Don't allow two generic parameters to be equivalent, because then we
|
||||
@@ -1151,6 +1149,108 @@ bool ArchetypeBuilder::inferRequirements(Pattern *pattern) {
|
||||
return walker.hadError();
|
||||
}
|
||||
|
||||
/// Perform typo correction on the given nested type, producing the
|
||||
/// corrected name (if successful).
|
||||
static Identifier typoCorrectNestedType(
|
||||
ArchetypeBuilder::PotentialArchetype *pa) {
|
||||
StringRef name = pa->getName().str();
|
||||
|
||||
// Look through all of the associated types of all of the protocols
|
||||
// to which the parent conforms.
|
||||
llvm::SmallVector<Identifier, 2> bestMatches;
|
||||
unsigned bestEditDistance = 0;
|
||||
unsigned maxScore = (name.size() + 1) / 3;
|
||||
for (const auto &conforms : pa->getParent()->getConformsTo()) {
|
||||
auto proto = conforms.first;
|
||||
for (auto member : proto->getMembers()) {
|
||||
auto assocType = dyn_cast<AssociatedTypeDecl>(member);
|
||||
if (!assocType)
|
||||
continue;
|
||||
|
||||
unsigned dist = name.edit_distance(assocType->getName().str(),
|
||||
/*allowReplacements=*/true,
|
||||
maxScore);
|
||||
assert(dist > 0 && "nested type should have matched associated type");
|
||||
if (bestEditDistance == 0 || dist == bestEditDistance) {
|
||||
bestEditDistance = dist;
|
||||
maxScore = bestEditDistance;
|
||||
bestMatches.push_back(assocType->getName());
|
||||
} else if (dist < bestEditDistance) {
|
||||
bestEditDistance = dist;
|
||||
maxScore = bestEditDistance;
|
||||
bestMatches.clear();
|
||||
bestMatches.push_back(assocType->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find any matches at all, fail.
|
||||
if (bestMatches.empty())
|
||||
return Identifier();
|
||||
|
||||
// Make sure that we didn't find more than one match at the best
|
||||
// edit distance.
|
||||
for (auto other : llvm::makeArrayRef(bestMatches).slice(1)) {
|
||||
if (other != bestMatches.front())
|
||||
return Identifier();
|
||||
}
|
||||
|
||||
return bestMatches.front();
|
||||
}
|
||||
|
||||
bool ArchetypeBuilder::finalize(SourceLoc loc) {
|
||||
bool invalid = false;
|
||||
|
||||
// If any nested types remain unresolved, produce diagnostics.
|
||||
if (Impl->NumUnresolvedNestedTypes > 0) {
|
||||
visitPotentialArchetypes([&](PotentialArchetype *pa) {
|
||||
// We only care about nested types that haven't been resolved.
|
||||
if (pa->getParent() == nullptr || pa->getResolvedAssociatedType() ||
|
||||
pa->getRepresentative() != pa ||
|
||||
/* FIXME: Should be able to handle this earlier */pa->getSuperclass())
|
||||
return;
|
||||
|
||||
assert(!pa->getUnresolvedReferences().empty() &&
|
||||
"Missing unresolved references?");
|
||||
|
||||
// Try to typo correct to a nested type name.
|
||||
Identifier correction = typoCorrectNestedType(pa);
|
||||
SourceLoc diagLoc = pa->getUnresolvedReferences().front()->getIdLoc();
|
||||
|
||||
// Typo correction failed; a diagnostic will be emitted later.
|
||||
if (correction.empty()) {
|
||||
invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Typo correction succeeded; emit Fix-Its and update the
|
||||
// component ids.
|
||||
auto diag = Diags.diagnose(diagLoc, diag::invalid_member_type_suggest,
|
||||
pa->getParent()->getDependentType(*this),
|
||||
pa->getName(),
|
||||
correction);
|
||||
|
||||
for (auto comp : pa->getUnresolvedReferences()) {
|
||||
diag.fixItReplace(comp->getIdLoc(), correction.str());
|
||||
comp->overwriteIdentifier(correction);
|
||||
}
|
||||
|
||||
// Resolve the associated type and merge the potential archetypes.
|
||||
pa->setInvalid();
|
||||
auto replacement = pa->getParent()->getNestedType(correction, *this,
|
||||
nullptr);
|
||||
pa->resolveAssociatedType(replacement->getResolvedAssociatedType(),
|
||||
*this);
|
||||
addSameTypeRequirementBetweenArchetypes(
|
||||
pa, replacement,
|
||||
RequirementSource(RequirementSource::Protocol, diagLoc));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return invalid;
|
||||
}
|
||||
|
||||
ArchetypeType *
|
||||
ArchetypeBuilder::getArchetype(GenericTypeParamDecl *GenericParam) {
|
||||
auto known = Impl->PotentialArchetypes.find(
|
||||
@@ -1226,6 +1326,11 @@ void ArchetypeBuilder::visitPotentialArchetypes(F f) {
|
||||
template<typename F>
|
||||
void ArchetypeBuilder::enumerateRequirements(F f) {
|
||||
visitPotentialArchetypes([&](PotentialArchetype *archetype) {
|
||||
// Invalid archetypes are never representatives in well-formed or corrected
|
||||
// signature, so we don't need to visit them.
|
||||
if (archetype->isInvalid())
|
||||
return;
|
||||
|
||||
// If this is not the representative, produce a same-type
|
||||
// constraint to the representative.
|
||||
if (archetype->getRepresentative() != archetype) {
|
||||
|
||||
@@ -651,6 +651,9 @@ bool TypeChecker::validateGenericFuncSignature(AbstractFunctionDecl *func) {
|
||||
if (func->hasType())
|
||||
return !func->isInvalid();
|
||||
|
||||
// Finalize the generic requirements.
|
||||
(void)builder.finalize(func->getLoc());
|
||||
|
||||
// The archetype builder now has all of the requirements, although there might
|
||||
// still be errors that have not yet been diagnosed. Revert the generic
|
||||
// function signature and type-check it again, completely.
|
||||
@@ -821,6 +824,9 @@ GenericSignature *TypeChecker::validateGenericSignature(
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
// Finalize the generic requirements.
|
||||
(void)builder.finalize(genericParams->getSourceRange().Start);
|
||||
|
||||
// The archetype builder now has all of the requirements, although there might
|
||||
// still be errors that have not yet been diagnosed. Revert the signature
|
||||
// and type-check it again, completely.
|
||||
|
||||
@@ -576,7 +576,6 @@ resolveIdentTypeComponent(TypeChecker &TC, DeclContext *DC,
|
||||
|
||||
// Otherwise, check for an ambiguity.
|
||||
if (current.is<Module *>() || !current.get<Type>()->isEqual(type)) {
|
||||
auto currentType = current.get<Type>();
|
||||
isAmbiguous = true;
|
||||
break;
|
||||
}
|
||||
|
||||
37
test/Generics/associated_type_typo.swift
Normal file
37
test/Generics/associated_type_typo.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
// RUN: %swift -parse %s -verify
|
||||
|
||||
// RUN: not %swift -parse -debug-generic-signatures %s > %t.dump 2>&1
|
||||
// RUN: FileCheck -check-prefix CHECK-GENERIC %s < %t.dump
|
||||
|
||||
protocol P1 {
|
||||
typealias Assoc
|
||||
}
|
||||
|
||||
protocol P2 {
|
||||
typealias AssocP2 : P1
|
||||
}
|
||||
|
||||
protocol P3 { }
|
||||
protocol P4 { }
|
||||
|
||||
// expected-error@+1{{'T' does not have a member type named 'assoc'; did you mean 'Assoc'?}}{{30-35=Assoc}}
|
||||
func typoAssoc1<T : P1>(x: T.assoc) { }
|
||||
|
||||
// expected-error@+1{{'T' does not have a member type named 'assoc'; did you mean 'Assoc'?}}{{40-45=Assoc}}{{51-56=Assoc}}
|
||||
func typoAssoc2<T : P1, U : P1 where T.assoc == U.assoc>() { }
|
||||
|
||||
// expected-error@+2{{'T.AssocP2' does not have a member type named 'assoc'; did you mean 'Assoc'?}}{{27-32=Assoc}}{{50-55=Assoc}}
|
||||
func typoAssoc3<T : P2, U : P2 where
|
||||
U.AssocP2.assoc : P3, T.AssocP2.assoc : P4,
|
||||
T.AssocP2 == U.AssocP2>() { }
|
||||
|
||||
// expected-error@+2{{'T' does not have a member type named 'Assocp2'; did you mean 'AssocP2'?}}{{32-39=AssocP2}}
|
||||
// expected-error@+1{{'T.AssocP2' does not have a member type named 'assoc'; did you mean 'Assoc'?}}{{40-45=Assoc}}
|
||||
func typoAssoc4<T : P2 where T.Assocp2.assoc : P3>() { }
|
||||
|
||||
// CHECK-GENERIC-LABEL: .typoAssoc4()@
|
||||
// CHECK-NEXT: Requirements:
|
||||
// CHECK-NEXT: T : P2 [explicit
|
||||
// CHECK-NEXT: T[.P2].AssocP2 == T.AssocP2 [protocol
|
||||
// CHECK-NEXT: T[.P2].AssocP2[.P1].Assoc : P3 [explicit
|
||||
// CHECK-NEXT: Generic signature
|
||||
Reference in New Issue
Block a user