Skip to content

Commit eb68737

Browse files
authored
Merge pull request #4936 from jrose-apple/swift-3-generic-param-fix-it
Provide a fix-it that inserts omitted generic parameters when they can't be inferred.
2 parents 822e3dd + d8a3757 commit eb68737

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
@@ -6488,39 +6488,112 @@ void ConstraintSystem::diagnoseFailureForExpr(Expr *expr) {
64886488
diagnosis.diagnoseAmbiguity(expr);
64896489
}
64906490

6491+
static bool hasArchetype(const GenericTypeDecl *generic,
6492+
const ArchetypeType *archetype) {
6493+
return std::any_of(generic->getInnermostGenericParamTypes().begin(),
6494+
generic->getInnermostGenericParamTypes().end(),
6495+
[archetype](const GenericTypeParamType *genericParamTy) {
6496+
return genericParamTy->getDecl()->getArchetype() == archetype;
6497+
});
6498+
}
6499+
64916500
static void noteArchetypeSource(const TypeLoc &loc, ArchetypeType *archetype,
6492-
TypeChecker &tc) {
6493-
GenericTypeDecl *FoundDecl = nullptr;
6494-
6501+
ConstraintSystem &cs) {
6502+
const GenericTypeDecl *FoundDecl = nullptr;
6503+
const ComponentIdentTypeRepr *FoundGenericTypeBase = nullptr;
6504+
64956505
// Walk the TypeRepr to find the type in question.
64966506
if (auto typerepr = loc.getTypeRepr()) {
64976507
struct FindGenericTypeDecl : public ASTWalker {
6498-
GenericTypeDecl *&FoundDecl;
6499-
FindGenericTypeDecl(GenericTypeDecl *&FoundDecl) : FoundDecl(FoundDecl) {
6500-
}
6508+
const GenericTypeDecl *FoundDecl = nullptr;
6509+
const ComponentIdentTypeRepr *FoundGenericTypeBase = nullptr;
6510+
const ArchetypeType *Archetype;
6511+
6512+
FindGenericTypeDecl(const ArchetypeType *Archetype)
6513+
: Archetype(Archetype) {}
65016514

65026515
bool walkToTypeReprPre(TypeRepr *T) override {
65036516
// If we already emitted the note, we're done.
65046517
if (FoundDecl) return false;
65056518

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

65266599

@@ -6636,11 +6709,7 @@ void FailureDiagnosis::diagnoseUnboundArchetype(ArchetypeType *archetype,
66366709
.highlight(ECE->getCastTypeLoc().getSourceRange());
66376710

66386711
// Emit a note specifying where this came from, if we can find it.
6639-
noteArchetypeSource(ECE->getCastTypeLoc(), archetype, tc);
6640-
if (auto *ND = ECE->getCastTypeLoc().getType()
6641-
->getNominalOrBoundGenericNominal())
6642-
tc.diagnose(ND, diag::archetype_declared_in_type, archetype,
6643-
ND->getDeclaredType());
6712+
noteArchetypeSource(ECE->getCastTypeLoc(), archetype, *CS);
66446713
return;
66456714
}
66466715

@@ -6670,7 +6739,7 @@ void FailureDiagnosis::diagnoseUnboundArchetype(ArchetypeType *archetype,
66706739

66716740
if (auto TE = dyn_cast<TypeExpr>(anchor)) {
66726741
// Emit a note specifying where this came from, if we can find it.
6673-
noteArchetypeSource(TE->getTypeLoc(), archetype, tc);
6742+
noteArchetypeSource(TE->getTypeLoc(), archetype, *CS);
66746743
return;
66756744
}
66766745

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)