Skip to content

Commit d8a3757

Browse files
committed
Merge pull request #4682 from jrose-apple/generic-param-fix-it
Provide a fix-it that inserts omitted generic parameters when they can't be inferred. rdar://problem/27087345
1 parent bca63b4 commit d8a3757

File tree

13 files changed

+353
-74
lines changed

13 files changed

+353
-74
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,8 @@ ERROR(unbound_generic_parameter_cast,none,
850850
"generic parameter %0 could not be inferred in cast to %1", (Type, Type))
851851
NOTE(archetype_declared_in_type,none,
852852
"%0 declared as parameter to type %1", (Type, Type))
853+
NOTE(unbound_generic_parameter_explicit_fix,none,
854+
"explicitly specify the generic arguments to fix this issue", ())
853855

854856

855857
ERROR(string_index_not_integer,none,

lib/FrontendTool/FrontendTool.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,8 @@ class JSONFixitWriter : public DiagnosticConsumer {
648648
Info.ID == diag::deprecated_protocol_composition.ID ||
649649
Info.ID == diag::deprecated_protocol_composition_single.ID ||
650650
Info.ID == diag::deprecated_any_composition.ID ||
651-
Info.ID == diag::deprecated_operator_body.ID)
651+
Info.ID == diag::deprecated_operator_body.ID ||
652+
Info.ID == diag::unbound_generic_parameter_explicit_fix.ID)
652653
return true;
653654

654655
return false;

lib/Sema/CSDiag.cpp

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6478,39 +6478,112 @@ void ConstraintSystem::diagnoseFailureForExpr(Expr *expr) {
64786478
diagnosis.diagnoseAmbiguity(expr);
64796479
}
64806480

6481+
static bool hasArchetype(const GenericTypeDecl *generic,
6482+
const ArchetypeType *archetype) {
6483+
return std::any_of(generic->getInnermostGenericParamTypes().begin(),
6484+
generic->getInnermostGenericParamTypes().end(),
6485+
[archetype](const GenericTypeParamType *genericParamTy) {
6486+
return genericParamTy->getDecl()->getArchetype() == archetype;
6487+
});
6488+
}
6489+
64816490
static void noteArchetypeSource(const TypeLoc &loc, ArchetypeType *archetype,
6482-
TypeChecker &tc) {
6483-
GenericTypeDecl *FoundDecl = nullptr;
6484-
6491+
ConstraintSystem &cs) {
6492+
const GenericTypeDecl *FoundDecl = nullptr;
6493+
const ComponentIdentTypeRepr *FoundGenericTypeBase = nullptr;
6494+
64856495
// Walk the TypeRepr to find the type in question.
64866496
if (auto typerepr = loc.getTypeRepr()) {
64876497
struct FindGenericTypeDecl : public ASTWalker {
6488-
GenericTypeDecl *&FoundDecl;
6489-
FindGenericTypeDecl(GenericTypeDecl *&FoundDecl) : FoundDecl(FoundDecl) {
6490-
}
6498+
const GenericTypeDecl *FoundDecl = nullptr;
6499+
const ComponentIdentTypeRepr *FoundGenericTypeBase = nullptr;
6500+
const ArchetypeType *Archetype;
6501+
6502+
FindGenericTypeDecl(const ArchetypeType *Archetype)
6503+
: Archetype(Archetype) {}
64916504

64926505
bool walkToTypeReprPre(TypeRepr *T) override {
64936506
// If we already emitted the note, we're done.
64946507
if (FoundDecl) return false;
64956508

6496-
if (auto ident = dyn_cast<ComponentIdentTypeRepr>(T))
6497-
FoundDecl = dyn_cast_or_null<GenericTypeDecl>(ident->getBoundDecl());
6509+
if (auto ident = dyn_cast<ComponentIdentTypeRepr>(T)) {
6510+
auto *generic =
6511+
dyn_cast_or_null<GenericTypeDecl>(ident->getBoundDecl());
6512+
if (hasArchetype(generic, Archetype)) {
6513+
FoundDecl = generic;
6514+
FoundGenericTypeBase = ident;
6515+
return false;
6516+
}
6517+
}
64986518
// Keep walking.
64996519
return true;
65006520
}
6501-
} findGenericTypeDecl(FoundDecl);
6502-
6521+
} findGenericTypeDecl(archetype);
6522+
65036523
typerepr->walk(findGenericTypeDecl);
6524+
FoundDecl = findGenericTypeDecl.FoundDecl;
6525+
FoundGenericTypeBase = findGenericTypeDecl.FoundGenericTypeBase;
65046526
}
6505-
6527+
65066528
// If we didn't find the type in the TypeRepr, fall back to the type in the
65076529
// type checked expression.
6508-
if (!FoundDecl)
6509-
FoundDecl = loc.getType()->getAnyGeneric();
6510-
6511-
if (FoundDecl)
6512-
tc.diagnose(FoundDecl, diag::archetype_declared_in_type, archetype,
6513-
FoundDecl->getDeclaredType());
6530+
if (!FoundDecl) {
6531+
if (const GenericTypeDecl *generic = loc.getType()->getAnyGeneric())
6532+
if (hasArchetype(generic, archetype))
6533+
FoundDecl = generic;
6534+
}
6535+
6536+
auto &tc = cs.getTypeChecker();
6537+
if (FoundDecl) {
6538+
tc.diagnose(FoundDecl, diag::archetype_declared_in_type,
6539+
archetype, FoundDecl->getDeclaredType());
6540+
}
6541+
6542+
if (FoundGenericTypeBase && !isa<GenericIdentTypeRepr>(FoundGenericTypeBase)){
6543+
assert(FoundDecl);
6544+
6545+
// If we can, prefer using any types already fixed by the constraint system.
6546+
// This lets us produce fixes like `Pair<Int, Any>` instead of defaulting to
6547+
// `Pair<Any, Any>`.
6548+
// Right now we only handle this when the type that's at fault is the
6549+
// top-level type passed to this function.
6550+
ArrayRef<Type> genericArgs;
6551+
if (auto *boundGenericTy = loc.getType()->getAs<BoundGenericType>()) {
6552+
if (boundGenericTy->getDecl() == FoundDecl)
6553+
genericArgs = boundGenericTy->getGenericArgs();
6554+
}
6555+
6556+
auto getPreferredType =
6557+
[&](const GenericTypeParamDecl *genericParam) -> Type {
6558+
// If we were able to get the generic arguments (i.e. the types used at
6559+
// FoundDecl's use site), we can prefer those...
6560+
if (genericArgs.empty())
6561+
return Type();
6562+
6563+
Type preferred = genericArgs[genericParam->getIndex()];
6564+
if (!preferred || preferred->is<ErrorType>())
6565+
return Type();
6566+
6567+
// ...but only if they were actually resolved by the constraint system
6568+
// despite the failure.
6569+
TypeVariableType *unused;
6570+
Type maybeFixedType = cs.getFixedTypeRecursive(preferred, unused,
6571+
/*wantRValue*/true);
6572+
if (maybeFixedType->hasTypeVariable() ||
6573+
maybeFixedType->hasUnresolvedType()) {
6574+
return Type();
6575+
}
6576+
return maybeFixedType;
6577+
};
6578+
6579+
SmallString<64> genericParamBuf;
6580+
if (tc.getDefaultGenericArgumentsString(genericParamBuf, FoundDecl,
6581+
getPreferredType)) {
6582+
tc.diagnose(FoundGenericTypeBase->getLoc(),
6583+
diag::unbound_generic_parameter_explicit_fix)
6584+
.fixItInsertAfter(FoundGenericTypeBase->getEndLoc(), genericParamBuf);
6585+
}
6586+
}
65146587
}
65156588

65166589

@@ -6626,11 +6699,7 @@ void FailureDiagnosis::diagnoseUnboundArchetype(ArchetypeType *archetype,
66266699
.highlight(ECE->getCastTypeLoc().getSourceRange());
66276700

66286701
// Emit a note specifying where this came from, if we can find it.
6629-
noteArchetypeSource(ECE->getCastTypeLoc(), archetype, tc);
6630-
if (auto *ND = ECE->getCastTypeLoc().getType()
6631-
->getNominalOrBoundGenericNominal())
6632-
tc.diagnose(ND, diag::archetype_declared_in_type, archetype,
6633-
ND->getDeclaredType());
6702+
noteArchetypeSource(ECE->getCastTypeLoc(), archetype, *CS);
66346703
return;
66356704
}
66366705

@@ -6660,7 +6729,7 @@ void FailureDiagnosis::diagnoseUnboundArchetype(ArchetypeType *archetype,
66606729

66616730
if (auto TE = dyn_cast<TypeExpr>(anchor)) {
66626731
// Emit a note specifying where this came from, if we can find it.
6663-
noteArchetypeSource(TE->getTypeLoc(), archetype, tc);
6732+
noteArchetypeSource(TE->getTypeLoc(), archetype, *CS);
66646733
return;
66656734
}
66666735

lib/Sema/MiscDiagnostics.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,69 @@ static void diagnoseImplicitSelfUseInClosure(TypeChecker &TC, const Expr *E,
938938
const_cast<Expr *>(E)->walk(DiagnoseWalker(TC, isAlreadyInClosure));
939939
}
940940

941+
bool TypeChecker::getDefaultGenericArgumentsString(
942+
SmallVectorImpl<char> &buf,
943+
const swift::GenericTypeDecl *typeDecl,
944+
llvm::function_ref<Type(const GenericTypeParamDecl *)> getPreferredType) {
945+
llvm::raw_svector_ostream genericParamText{buf};
946+
genericParamText << "<";
947+
948+
auto printGenericParamSummary =
949+
[&](const GenericTypeParamType *genericParamTy) {
950+
const GenericTypeParamDecl *genericParam = genericParamTy->getDecl();
951+
if (Type result = getPreferredType(genericParam)) {
952+
result.print(genericParamText);
953+
return;
954+
}
955+
956+
ArrayRef<ProtocolDecl *> protocols =
957+
genericParam->getConformingProtocols(this);
958+
959+
if (Type superclass = genericParam->getSuperclass()) {
960+
if (protocols.empty()) {
961+
superclass.print(genericParamText);
962+
return;
963+
}
964+
965+
genericParamText << "<#" << genericParam->getName() << ": ";
966+
superclass.print(genericParamText);
967+
for (const ProtocolDecl *proto : protocols) {
968+
if (proto->isSpecificProtocol(KnownProtocolKind::AnyObject))
969+
continue;
970+
genericParamText << " & " << proto->getName();
971+
}
972+
genericParamText << "#>";
973+
return;
974+
}
975+
976+
if (protocols.empty()) {
977+
genericParamText << Context.Id_Any;
978+
return;
979+
}
980+
981+
if (protocols.size() == 1 &&
982+
(protocols.front()->isObjC() ||
983+
protocols.front()->isSpecificProtocol(KnownProtocolKind::AnyObject))) {
984+
genericParamText << protocols.front()->getName();
985+
return;
986+
}
987+
988+
genericParamText << "<#" << genericParam->getName() << ": ";
989+
interleave(protocols,
990+
[&](const ProtocolDecl *proto) {
991+
genericParamText << proto->getName();
992+
},
993+
[&] { genericParamText << " & "; });
994+
genericParamText << "#>";
995+
};
996+
997+
interleave(typeDecl->getInnermostGenericParamTypes(),
998+
printGenericParamSummary, [&]{ genericParamText << ", "; });
999+
1000+
genericParamText << ">";
1001+
return true;
1002+
}
1003+
9411004
/// Diagnose an argument labeling issue, returning true if we successfully
9421005
/// diagnosed the issue.
9431006
bool swift::diagnoseArgumentLabelError(TypeChecker &TC, const Expr *expr,

lib/Sema/TypeCheckType.cpp

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -622,41 +622,9 @@ static void diagnoseUnboundGenericType(TypeChecker &tc, Type ty,SourceLoc loc) {
622622
InFlightDiagnostic diag = tc.diagnose(loc,
623623
diag::generic_type_requires_arguments, ty);
624624
if (auto *genericD = unbound->getDecl()) {
625-
626-
// Tries to infer the type arguments to pass.
627-
// Currently it only works if all the generic arguments have a super type,
628-
// or it requires a class, in which case it infers 'AnyObject'.
629-
auto inferGenericArgs = [](GenericTypeDecl *genericD)->std::string {
630-
GenericParamList *genParamList = genericD->getGenericParams();
631-
if (!genParamList)
632-
return std::string();
633-
auto params= genParamList->getParams();
634-
if (params.empty())
635-
return std::string();
636-
std::string argsToAdd = "<";
637-
for (unsigned i = 0, e = params.size(); i != e; ++i) {
638-
auto param = params[i];
639-
auto archTy = param->getArchetype();
640-
if (!archTy)
641-
return std::string();
642-
if (auto superTy = archTy->getSuperclass()) {
643-
argsToAdd += superTy.getString();
644-
} else if (archTy->requiresClass()) {
645-
argsToAdd += "AnyObject";
646-
} else {
647-
return std::string(); // give up.
648-
}
649-
if (i < e-1)
650-
argsToAdd += ", ";
651-
}
652-
argsToAdd += ">";
653-
return argsToAdd;
654-
};
655-
656-
std::string genericArgsToAdd = inferGenericArgs(genericD);
657-
if (!genericArgsToAdd.empty()) {
625+
SmallString<64> genericArgsToAdd;
626+
if (tc.getDefaultGenericArgumentsString(genericArgsToAdd, genericD))
658627
diag.fixItInsertAfter(loc, genericArgsToAdd);
659-
}
660628
}
661629
}
662630
tc.diagnose(unbound->getDecl()->getLoc(), diag::generic_type_declared_here,

lib/Sema/TypeChecker.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,23 @@ class TypeChecker final : public LazyResolver {
19681968
ConstructorDecl *ctorDecl,
19691969
bool SuppressDiagnostics);
19701970

1971+
/// Builds a string representing a "default" generic argument list for
1972+
/// \p typeDecl. In general, this means taking the bound of each generic
1973+
/// parameter. The \p getPreferredType callback can be used to provide a
1974+
/// different type from the bound.
1975+
///
1976+
/// It may not always be possible to find a single appropriate type for a
1977+
/// particular parameter (say, if it has two bounds). In this case, an
1978+
/// Xcode-style placeholder will be used instead.
1979+
///
1980+
/// Returns true if the arguments list could be constructed, false if for
1981+
/// some reason it could not.
1982+
bool getDefaultGenericArgumentsString(
1983+
SmallVectorImpl<char> &buf,
1984+
const GenericTypeDecl *typeDecl,
1985+
llvm::function_ref<Type(const GenericTypeParamDecl *)> getPreferredType =
1986+
[](const GenericTypeParamDecl *) { return Type(); });
1987+
19711988
/// Attempt to omit needless words from the name of the given declaration.
19721989
Optional<DeclName> omitNeedlessWords(AbstractFunctionDecl *afd);
19731990

test/Constraints/bridging.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func rdar20029786(_ ns: NSString?) {
298298

299299
// <rdar://problem/19813772> QoI: Using as! instead of as in this case produces really bad diagnostic
300300
func rdar19813772(_ nsma: NSMutableArray) {
301-
var a1 = nsma as! Array // expected-error{{generic parameter 'Element' could not be inferred in cast to 'Array<_>'}}
301+
var a1 = nsma as! Array // expected-error{{generic parameter 'Element' could not be inferred in cast to 'Array<_>'}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{26-26=<Any>}}
302302
// FIXME: The following diagnostic is misleading and should not happen: expected-warning@-1{{cast from 'NSMutableArray' to unrelated type 'Array<_>' always fails}}
303303
var a2 = nsma as! Array<AnyObject> // expected-warning{{forced cast from 'NSMutableArray' to 'Array<AnyObject>' always succeeds; did you mean to use 'as'?}} {{17-20=as}}
304304
var a3 = nsma as Array<AnyObject>

test/Constraints/construction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ acceptString("\(hello), \(world) #\(i)!")
5757
Optional<Int>(1) // expected-warning{{unused}}
5858
Optional(1) // expected-warning{{unused}}
5959
_ = .none as Optional<Int>
60-
Optional(.none) // expected-error{{generic parameter 'T' could not be inferred}}
60+
Optional(.none) // expected-error{{generic parameter 'T' could not be inferred}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{9-9=<Any>}}
6161

6262
// Interpolation
6363
_ = "\(hello), \(world) #\(i)!"

0 commit comments

Comments
 (0)