Skip to content

Commit ddc2065

Browse files
Merge pull request #41198 from AnthonyLatsis/se-309-fixit
[SE-309] CSDiag: Add a fix-it that replaces an existential parameter type with its generic counterpart
2 parents 350d039 + 2342712 commit ddc2065

File tree

9 files changed

+515
-184
lines changed

9 files changed

+515
-184
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ ERROR(could_not_use_instance_member_on_type,none,
140140
"%select{| instance of nested}3 type %0",
141141
(Type, DeclNameRef, Type, bool))
142142
ERROR(could_not_use_member_on_existential,none,
143-
"member %1 cannot be used on value of protocol type %0; use a generic"
144-
" constraint instead",
143+
"member %1 cannot be used on value of protocol type %0; consider using a"
144+
" generic constraint instead",
145145
(Type, DeclNameRef))
146146
FIXIT(replace_with_type,"%0",(Type))
147147
FIXIT(insert_type_qualification,"%0.",(Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3933,19 +3933,145 @@ bool UnintendedExtraGenericParamMemberFailure::diagnoseAsError() {
39333933
}
39343934

39353935
bool InvalidMemberRefOnExistential::diagnoseAsError() {
3936-
auto anchor = getRawAnchor();
3936+
const auto Anchor = getRawAnchor();
39373937

3938-
DeclNameLoc nameLoc;
3939-
if (auto *UDE = getAsExpr<UnresolvedDotExpr>(anchor)) {
3940-
nameLoc = UDE->getNameLoc();
3941-
} else if (auto *UME = getAsExpr<UnresolvedMemberExpr>(anchor)) {
3942-
nameLoc = UME->getNameLoc();
3938+
DeclNameLoc NameLoc;
3939+
ParamDecl *PD = nullptr;
3940+
if (auto *UDE = getAsExpr<UnresolvedDotExpr>(Anchor)) {
3941+
NameLoc = UDE->getNameLoc();
3942+
if (auto *DRE = dyn_cast<DeclRefExpr>(UDE->getBase())) {
3943+
PD = dyn_cast<ParamDecl>(DRE->getDecl());
3944+
}
3945+
} else if (auto *UME = getAsExpr<UnresolvedMemberExpr>(Anchor)) {
3946+
NameLoc = UME->getNameLoc();
3947+
} else if (auto *SE = getAsExpr<SubscriptExpr>(Anchor)) {
3948+
if (auto *DRE = dyn_cast<DeclRefExpr>(SE->getBase())) {
3949+
PD = dyn_cast<ParamDecl>(DRE->getDecl());
3950+
}
39433951
}
39443952

3945-
emitDiagnostic(diag::could_not_use_member_on_existential, getBaseType(),
3946-
getName())
3947-
.highlight(nameLoc.getSourceRange())
3948-
.highlight(getSourceRange());
3953+
auto Diag = emitDiagnostic(diag::could_not_use_member_on_existential,
3954+
getBaseType(), getName());
3955+
Diag.highlight(NameLoc.getSourceRange());
3956+
Diag.highlight(getSourceRange());
3957+
3958+
// If the base expression is a reference to a function or subscript
3959+
// parameter, offer a fixit that replaces the existential parameter type with
3960+
// its generic equivalent, e.g. func foo(p: any P) → func foo<T: P>(p: T).
3961+
// FIXME: Add an option to use 'some' vs. an explicit generic parameter.
3962+
3963+
if (!PD || !PD->getDeclContext()->getAsDecl())
3964+
return true;
3965+
3966+
// Code inside a subscript is bound against a duplicate set of implicit
3967+
// accessor parameters, which don't have a TypeRepr; dig out the corresponding
3968+
// explicit subscript parameter.
3969+
if (auto *const AD =
3970+
dyn_cast<AccessorDecl>(PD->getDeclContext()->getAsDecl())) {
3971+
auto *const SD = dyn_cast<SubscriptDecl>(AD->getStorage());
3972+
if (!SD)
3973+
return true;
3974+
3975+
const auto AccessorParams = AD->getParameters()->getArray();
3976+
const unsigned idx =
3977+
llvm::find(AccessorParams, PD) - AccessorParams.begin();
3978+
3979+
switch (AD->getAccessorKind()) {
3980+
case AccessorKind::Set:
3981+
case AccessorKind::WillSet:
3982+
case AccessorKind::DidSet:
3983+
// Ignore references to the 'newValue' or 'oldValue' parameters.
3984+
if (AccessorParams.front() == PD) {
3985+
return true;
3986+
}
3987+
3988+
PD = SD->getIndices()->get(idx - 1);
3989+
break;
3990+
3991+
case AccessorKind::Get:
3992+
case AccessorKind::Read:
3993+
case AccessorKind::Modify:
3994+
case AccessorKind::Address:
3995+
case AccessorKind::MutableAddress:
3996+
PD = SD->getIndices()->get(idx);
3997+
break;
3998+
}
3999+
}
4000+
4001+
// Bail out in the absence of a TypeRepr.
4002+
if (!PD->getTypeRepr())
4003+
return true;
4004+
4005+
// Give up on 'inout' parameters. The intent is far more vague in this case,
4006+
// and applying the fix-it would invalidate mutations.
4007+
if (PD->isInOut())
4008+
return true;
4009+
4010+
constexpr StringRef GPNamePlaceholder = "<#generic parameter name#>";
4011+
SourceRange TyReplacementRange;
4012+
SourceRange RemoveAnyRange;
4013+
SourceLoc GPDeclLoc;
4014+
std::string GPDeclStr;
4015+
{
4016+
llvm::raw_string_ostream OS(GPDeclStr);
4017+
auto *const GC = PD->getDeclContext()->getAsDecl()->getAsGenericContext();
4018+
if (GC->getParsedGenericParams()) {
4019+
GPDeclLoc = GC->getParsedGenericParams()->getRAngleLoc();
4020+
OS << ", ";
4021+
} else {
4022+
GPDeclLoc =
4023+
isa<AbstractFunctionDecl>(GC)
4024+
? cast<AbstractFunctionDecl>(GC)->getParameters()->getLParenLoc()
4025+
: cast<SubscriptDecl>(GC)->getIndices()->getLParenLoc();
4026+
OS << "<";
4027+
}
4028+
OS << GPNamePlaceholder << ": ";
4029+
4030+
auto *TR = PD->getTypeRepr()->getWithoutParens();
4031+
if (auto *STR = dyn_cast<SpecifierTypeRepr>(TR)) {
4032+
TR = STR->getBase()->getWithoutParens();
4033+
}
4034+
if (auto *ETR = dyn_cast<ExistentialTypeRepr>(TR)) {
4035+
TR = ETR->getConstraint();
4036+
RemoveAnyRange = SourceRange(ETR->getAnyLoc(), TR->getStartLoc());
4037+
TR = TR->getWithoutParens();
4038+
}
4039+
if (auto *MTR = dyn_cast<MetatypeTypeRepr>(TR)) {
4040+
TR = MTR->getBase();
4041+
4042+
// (P & Q).Type -> T.Type
4043+
// (P).Type -> (T).Type
4044+
// ((P & Q)).Type -> ((T)).Type
4045+
if (auto *TTR = dyn_cast<TupleTypeRepr>(TR)) {
4046+
assert(TTR->isParenType());
4047+
if (!isa<CompositionTypeRepr>(TTR->getElementType(0))) {
4048+
TR = TR->getWithoutParens();
4049+
}
4050+
}
4051+
}
4052+
TyReplacementRange = TR->getSourceRange();
4053+
4054+
// Strip any remaining parentheses and print the conformance constraint.
4055+
TR->getWithoutParens()->print(OS);
4056+
4057+
if (!GC->getParsedGenericParams()) {
4058+
OS << ">";
4059+
}
4060+
}
4061+
4062+
// First, replace the constraint type with the generic parameter type
4063+
// placeholder.
4064+
Diag.fixItReplace(TyReplacementRange, GPNamePlaceholder);
4065+
4066+
// Remove 'any' if needed, using a character-based removal to pick up
4067+
// whitespaces between it and its constraint repr.
4068+
if (RemoveAnyRange.isValid()) {
4069+
Diag.fixItRemoveChars(RemoveAnyRange.Start, RemoveAnyRange.End);
4070+
}
4071+
4072+
// Finally, insert the generic parameter declaration.
4073+
Diag.fixItInsert(GPDeclLoc, GPDeclStr);
4074+
39494075
return true;
39504076
}
39514077

lib/Sema/TypeCheckDecl.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,7 @@ bool TypeChecker::isAvailabilitySafeForConformance(
16111611
return requirementInfo.isContainedIn(witnessInfo);
16121612
}
16131613

1614-
// Returns 'nullptr' if this is the setter's 'newValue' parameter;
1614+
// Returns 'nullptr' if this is the 'newValue' or 'oldValue' parameter;
16151615
// otherwise, returns the corresponding parameter of the subscript
16161616
// declaration.
16171617
static ParamDecl *getOriginalParamFromAccessor(AbstractStorageDecl *storage,
@@ -1623,11 +1623,9 @@ static ParamDecl *getOriginalParamFromAccessor(AbstractStorageDecl *storage,
16231623
switch (accessor->getAccessorKind()) {
16241624
case AccessorKind::DidSet:
16251625
case AccessorKind::WillSet:
1626-
return nullptr;
1627-
16281626
case AccessorKind::Set:
16291627
if (param == accessorParams->get(0)) {
1630-
// This is the 'newValue' parameter.
1628+
// This is the 'newValue' or 'oldValue' parameter.
16311629
return nullptr;
16321630
}
16331631

test/Constraints/opened_existentials.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func testReturningOpaqueTypes(p: any P) {
164164
let q = p.getQ()
165165
let _: Int = q // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}}
166166

167-
p.getCollectionOf() // expected-error{{member 'getCollectionOf' cannot be used on value of protocol type 'P'; use a generic constraint instead}}
167+
p.getCollectionOf() // expected-error{{member 'getCollectionOf' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}}
168168

169169
let q2 = getPQ(p)
170170
let _: Int = q2 // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}}

test/Constraints/protocols.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ func testClonableExistential(_ v: Clonable, _ vv: Clonable.Type) {
370370
let _: (Bool) -> Clonable? = id(vv.returnSelfIUOStatic as (Bool) -> Clonable?)
371371
let _: Clonable! = id(vv.returnSelfIUOStatic(true))
372372

373-
let _ = v.badClonerFn() // expected-error {{member 'badClonerFn' cannot be used on value of protocol type 'Clonable'; use a generic constraint instead}}
374-
let _ = v.veryBadClonerFn() // expected-error {{member 'veryBadClonerFn' cannot be used on value of protocol type 'Clonable'; use a generic constraint instead}}
373+
let _ = v.badClonerFn() // expected-error {{member 'badClonerFn' cannot be used on value of protocol type 'Clonable'; consider using a generic constraint instead}}
374+
let _ = v.veryBadClonerFn() // expected-error {{member 'veryBadClonerFn' cannot be used on value of protocol type 'Clonable'; consider using a generic constraint instead}}
375375

376376
}
377377

test/Generics/function_defs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func existential<T : EqualComparable, U : EqualComparable>(_ t1: T, t2: T, u: U)
3838
eqComp = u
3939
if t1.isEqual(eqComp) {} // expected-error{{cannot convert value of type 'EqualComparable' to expected argument type 'T'}}
4040
if eqComp.isEqual(t2) {}
41-
// expected-error@-1 {{member 'isEqual' cannot be used on value of protocol type 'EqualComparable'; use a generic constraint instead}}
41+
// expected-error@-1 {{member 'isEqual' cannot be used on value of protocol type 'EqualComparable'; consider using a generic constraint instead}}
4242
}
4343

4444
protocol OtherEqualComparable {

0 commit comments

Comments
 (0)