Skip to content

Commit f239dbc

Browse files
committed
[SE-309] CSDiag: Add a fix-it that replaces an existential parameter type with its generic equivalent
1 parent d36aecc commit f239dbc

File tree

2 files changed

+342
-10
lines changed

2 files changed

+342
-10
lines changed

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

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P {
4+
associatedtype A
5+
6+
func method(_: A)
7+
subscript(_: A) -> A { get }
8+
init(_: A)
9+
10+
static func staticMethod(_: A)
11+
}
12+
protocol Q {}
13+
14+
do {
15+
func test(p: P) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
16+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:16--1:17=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
17+
}
18+
}
19+
do {
20+
func test(p: ((P))) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
21+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:18--1:19=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
22+
}
23+
}
24+
do {
25+
func test(p: any P) {
26+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:20--1:21=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
27+
}
28+
}
29+
do {
30+
func test(p: (inout any P)) {
31+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{none}}
32+
}
33+
}
34+
do {
35+
func test(p: __shared (any P)) {
36+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:30--1:31=<#generic parameter name#>}} {{-1:26--1:30=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
37+
}
38+
}
39+
do {
40+
func test(p: ((any P))) {
41+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:22--1:23=<#generic parameter name#>}} {{-1:18--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
42+
}
43+
}
44+
do {
45+
func test(p: any (P)) {
46+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type '(P)'; consider using a generic constraint instead}} {{-1:21--1:22=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
47+
}
48+
}
49+
do {
50+
func test(p: any P) {
51+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:22--1:23=<#generic parameter name#>}} {{-1:16--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
52+
}
53+
}
54+
do {
55+
func test(p: (any (P))) {
56+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type '(P)'; consider using a generic constraint instead}} {{-1:23--1:24=<#generic parameter name#>}} {{-1:17--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
57+
}
58+
}
59+
do {
60+
func test(p: P.Type) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
61+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type 'P.Type'; consider using a generic constraint instead}} {{-1:16--1:17=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
62+
}
63+
}
64+
do {
65+
func test(p: (P).Type) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
66+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '(P).Type'; consider using a generic constraint instead}} {{-1:17--1:18=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
67+
}
68+
}
69+
do {
70+
func test(p: any P.Type) {
71+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type 'P.Type'; consider using a generic constraint instead}} {{-1:20--1:21=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
72+
}
73+
}
74+
do {
75+
func test(p: any ((P).Type)) {
76+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '(P).Type'; consider using a generic constraint instead}} {{-1:22--1:23=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
77+
}
78+
}
79+
80+
do {
81+
func test(p: P & Q) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
82+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:16--1:21=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
83+
}
84+
}
85+
do {
86+
func test(p: ((P & Q))) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
87+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:18--1:23=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
88+
}
89+
}
90+
do {
91+
func test(p: any P & Q) {
92+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:20--1:25=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
93+
}
94+
}
95+
do {
96+
func test(p: inout any P & Q) {
97+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{none}}
98+
}
99+
}
100+
do {
101+
func test(p: __shared (any P & Q)) {
102+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:30--1:35=<#generic parameter name#>}} {{-1:26--1:30=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
103+
}
104+
}
105+
do {
106+
func test(p: ((any P & Q))) {
107+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:22--1:27=<#generic parameter name#>}} {{-1:18--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
108+
}
109+
}
110+
do {
111+
func test(p: any (P & Q)) {
112+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type '(P & Q)'; consider using a generic constraint instead}} {{-1:21--1:26=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
113+
}
114+
}
115+
do {
116+
func test(p: any P & Q) {
117+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:22--1:27=<#generic parameter name#>}} {{-1:16--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
118+
}
119+
}
120+
do {
121+
func test(p: (any (P & Q))) {
122+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type '(P & Q)'; consider using a generic constraint instead}} {{-1:23--1:28=<#generic parameter name#>}} {{-1:17--1:22=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
123+
}
124+
}
125+
do {
126+
func test(p: (P & Q).Type) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
127+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '(P & Q).Type'; consider using a generic constraint instead}} {{-1:16--1:23=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
128+
}
129+
}
130+
do {
131+
func test(p: ((P & Q)).Type) { // expected-warning {{protocol 'P' as a type must be explicitly marked as 'any'}}
132+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '((P & Q)).Type'; consider using a generic constraint instead}} {{-1:18--1:23=<#generic parameter name#>}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
133+
}
134+
}
135+
do {
136+
func test(p: any (P & Q).Type) {
137+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '(P & Q).Type'; consider using a generic constraint instead}} {{-1:20--1:27=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
138+
}
139+
}
140+
do {
141+
func test(p: any (((P & Q)).Type)) {
142+
p.staticMethod(false) // expected-error {{member 'staticMethod' cannot be used on value of protocol type '((P & Q)).Type'; consider using a generic constraint instead}} {{-1:23--1:28=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
143+
}
144+
}
145+
146+
do {
147+
// With an existing generic parameter list.
148+
func test<T: Sequence>(t: T, p: any P) {
149+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:39--1:40=<#generic parameter name#>}} {{-1:35--1:39=}} {{-1:24--1:24=, <#generic parameter name#>: P}} {{none}}
150+
}
151+
}
152+
do {
153+
// With an subscript expression.
154+
func test(p: any P) {
155+
p[false] // expected-error {{member 'subscript' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:20--1:21=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
156+
}
157+
}
158+
do {
159+
// With an initializer.
160+
func test(p: any P.Type) {
161+
p.init(false) // expected-error {{member 'init' cannot be used on value of protocol type 'P.Type'; consider using a generic constraint instead}} {{-1:20--1:21=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
162+
}
163+
}
164+
165+
// Inside initializers, accessors and subscripts.
166+
struct Test {
167+
init(p: any P) {
168+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:15--1:16=<#generic parameter name#>}} {{-1:11--1:15=}} {{-1:7--1:7=<<#generic parameter name#>: P>}} {{none}}
169+
}
170+
171+
init<T: P>(p: any P, t: T) {
172+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:21--1:22=<#generic parameter name#>}} {{-1:17--1:21=}} {{-1:12--1:12=, <#generic parameter name#>: P}} {{none}}
173+
}
174+
175+
subscript(p: any P) -> any P {
176+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-1:20--1:21=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P>}} {{none}}
177+
return p
178+
}
179+
180+
subscript<T: P>(p: any P, t: T) -> any P {
181+
get {
182+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-2:26--2:27=<#generic parameter name#>}} {{-2:22--2:26=}} {{-2:17--2:17=, <#generic parameter name#>: P}} {{none}}
183+
return p
184+
}
185+
set(value) {
186+
p.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-6:26--6:27=<#generic parameter name#>}} {{-6:22--6:26=}} {{-6:17--6:17=, <#generic parameter name#>: P}} {{none}}
187+
value.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{none}}
188+
}
189+
}
190+
191+
subscript(p: any P & Q) -> any P {
192+
_read { p.method(false) } // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-1:20--1:25=<#generic parameter name#>}} {{-1:16--1:20=}} {{-1:12--1:12=<<#generic parameter name#>: P & Q>}} {{none}}
193+
_modify { p.method(false) } // expected-error {{member 'method' cannot be used on value of protocol type 'P & Q'; consider using a generic constraint instead}} {{-2:20--2:25=<#generic parameter name#>}} {{-2:16--2:20=}} {{-2:12--2:12=<<#generic parameter name#>: P & Q>}} {{none}}
194+
willSet { p.method(false) } // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-3:20--3:25=<#generic parameter name#>}} {{-3:16--3:20=}} {{-3:12--3:12=<<#generic parameter name#>: P & Q>}} {{none}}
195+
didSet { p.method(false) } // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{-4:20--4:25=<#generic parameter name#>}} {{-4:16--4:20=}} {{-4:12--4:12=<<#generic parameter name#>: P & Q>}} {{none}}
196+
// expected-error@-2 {{'willSet' is not allowed in subscripts}}
197+
// expected-error@-2 {{'didSet' is not allowed in subscripts}}
198+
}
199+
200+
var property: any P {
201+
get {}
202+
set {
203+
newValue.method(false) // expected-error {{member 'method' cannot be used on value of protocol type 'P'; consider using a generic constraint instead}} {{none}}
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)