Skip to content

Commit 9fb198f

Browse files
committed
Sema: Do not diagnose preconcurrency redundancy in implied conformances
1 parent b97682a commit 9fb198f

File tree

3 files changed

+199
-11
lines changed

3 files changed

+199
-11
lines changed

include/swift/AST/ProtocolConformance.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
147147
SWIFT_INLINE_BITFIELD_EMPTY(RootProtocolConformance, ProtocolConformance);
148148

149149
SWIFT_INLINE_BITFIELD_FULL(NormalProtocolConformance, RootProtocolConformance,
150-
1+1+
150+
1+1+1+
151151
bitmax(NumProtocolConformanceOptions,8)+
152152
bitmax(NumProtocolConformanceStateBits,8)+
153153
bitmax(NumConformanceEntryKindBits,8),
@@ -157,6 +157,10 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
157157
/// populated any of its elements).
158158
HasComputedAssociatedConformances : 1,
159159

160+
/// Whether the preconcurrency attribute is effectful (not redundant) for
161+
/// this conformance.
162+
IsPreconcurrencyEffectful : 1,
163+
160164
: NumPadBits,
161165

162166
/// Options.
@@ -584,6 +588,7 @@ class NormalProtocolConformance : public RootProtocolConformance,
584588
"Cannot have a @preconcurrency location without isPreconcurrency");
585589
setState(state);
586590
Bits.NormalProtocolConformance.IsInvalid = false;
591+
Bits.NormalProtocolConformance.IsPreconcurrencyEffectful = false;
587592
Bits.NormalProtocolConformance.Options = options.toRaw();
588593
Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false;
589594
Bits.NormalProtocolConformance.SourceKind =
@@ -645,6 +650,20 @@ class NormalProtocolConformance : public RootProtocolConformance,
645650
(getOptions() | ProtocolConformanceFlags::Unchecked).toRaw();
646651
}
647652

653+
/// Whether the preconcurrency attribute is effectful (not redundant) for
654+
/// this conformance.
655+
bool isPreconcurrencyEffectful() const {
656+
ASSERT(isPreconcurrency() && isComplete());
657+
return Bits.NormalProtocolConformance.IsPreconcurrencyEffectful;
658+
}
659+
660+
/// Record that the preconcurrency attribute is effectful (not redundant)
661+
/// for this conformance.
662+
void setPreconcurrencyEffectful() {
663+
ASSERT(isPreconcurrency());
664+
Bits.NormalProtocolConformance.IsPreconcurrencyEffectful = true;
665+
}
666+
648667
/// Whether this is an preconcurrency conformance.
649668
bool isPreconcurrency() const {
650669
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,9 @@ class MultiConformanceChecker {
20122012
/// Determine whether the given requirement was left unsatisfied.
20132013
bool isUnsatisfiedReq(NormalProtocolConformance *conformance, ValueDecl *req);
20142014

2015+
/// Diagnose redundant `@preconcurrency` attributes on conformances.
2016+
void diagnoseRedundantPreconcurrency();
2017+
20152018
public:
20162019
MultiConformanceChecker(ASTContext &ctx) : Context(ctx) {}
20172020

@@ -2089,6 +2092,69 @@ static void diagnoseProtocolStubFixit(
20892092
NormalProtocolConformance *conformance,
20902093
ArrayRef<ASTContext::MissingWitness> missingWitnesses);
20912094

2095+
void MultiConformanceChecker::diagnoseRedundantPreconcurrency() {
2096+
// Collect explicit preconcurrency conformances for which preconcurrency is
2097+
// not directly effectful.
2098+
SmallVector<NormalProtocolConformance *, 2> explicitConformances;
2099+
for (auto *conformance : AllConformances) {
2100+
if (conformance->getSourceKind() == ConformanceEntryKind::Explicit &&
2101+
conformance->isPreconcurrency() &&
2102+
!conformance->isPreconcurrencyEffectful()) {
2103+
explicitConformances.push_back(conformance);
2104+
}
2105+
}
2106+
2107+
if (explicitConformances.empty()) {
2108+
return;
2109+
}
2110+
2111+
// If preconcurrency is effectful for an implied conformance (a conformance
2112+
// to an inherited protocol), consider it effectful for the explicit implying
2113+
// one.
2114+
for (auto *conformance : AllConformances) {
2115+
switch (conformance->getSourceKind()) {
2116+
case ConformanceEntryKind::Inherited:
2117+
case ConformanceEntryKind::PreMacroExpansion:
2118+
llvm_unreachable("Invalid normal protocol conformance kind");
2119+
case ConformanceEntryKind::Explicit:
2120+
case ConformanceEntryKind::Synthesized:
2121+
continue;
2122+
case ConformanceEntryKind::Implied:
2123+
if (!conformance->isPreconcurrency() ||
2124+
!conformance->isPreconcurrencyEffectful()) {
2125+
continue;
2126+
}
2127+
2128+
auto *proto = conformance->getProtocol();
2129+
for (auto *explicitConformance : explicitConformances) {
2130+
if (explicitConformance->getProtocol()->inheritsFrom(proto)) {
2131+
explicitConformance->setPreconcurrencyEffectful();
2132+
}
2133+
}
2134+
2135+
continue;
2136+
}
2137+
}
2138+
2139+
// Diagnose all explicit preconcurrency conformances for which preconcurrency
2140+
// is not effectful (redundant).
2141+
for (auto *conformance : explicitConformances) {
2142+
if (conformance->isPreconcurrencyEffectful()) {
2143+
continue;
2144+
}
2145+
2146+
auto diag = Context.Diags.diagnose(
2147+
conformance->getLoc(), diag::preconcurrency_conformance_not_used,
2148+
conformance->getProtocol()->getDeclaredInterfaceType());
2149+
2150+
SourceLoc preconcurrencyLoc = conformance->getPreconcurrencyLoc();
2151+
if (preconcurrencyLoc.isValid()) {
2152+
SourceLoc endLoc = preconcurrencyLoc.getAdvancedLoc(1);
2153+
diag.fixItRemove(SourceRange(preconcurrencyLoc, endLoc));
2154+
}
2155+
}
2156+
}
2157+
20922158
void MultiConformanceChecker::checkAllConformances() {
20932159
if (AllConformances.empty()) {
20942160
return;
@@ -2158,6 +2224,9 @@ void MultiConformanceChecker::checkAllConformances() {
21582224
}
21592225
}
21602226

2227+
// Diagnose any redundant preconcurrency.
2228+
this->diagnoseRedundantPreconcurrency();
2229+
21612230
// Emit diagnostics at the very end.
21622231
for (auto *conformance : AllConformances) {
21632232
emitDelayedDiags(conformance);
@@ -5353,16 +5422,8 @@ void ConformanceChecker::resolveValueWitnesses() {
53535422
}
53545423
}
53555424

5356-
if (Conformance->isPreconcurrency() && !usesPreconcurrency) {
5357-
auto diag = DC->getASTContext().Diags.diagnose(
5358-
Conformance->getLoc(), diag::preconcurrency_conformance_not_used,
5359-
Proto->getDeclaredInterfaceType());
5360-
5361-
SourceLoc preconcurrencyLoc = Conformance->getPreconcurrencyLoc();
5362-
if (preconcurrencyLoc.isValid()) {
5363-
SourceLoc endLoc = preconcurrencyLoc.getAdvancedLoc(1);
5364-
diag.fixItRemove(SourceRange(preconcurrencyLoc, endLoc));
5365-
}
5425+
if (Conformance->isPreconcurrency() && usesPreconcurrency) {
5426+
Conformance->setPreconcurrencyEffectful();
53665427
}
53675428

53685429
// Finally, check some ad-hoc protocol requirements.

test/Concurrency/preconcurrency_conformances.swift

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,111 @@ do {
198198
func b() {} // Ok
199199
}
200200
}
201+
202+
do {
203+
protocol P1 {}
204+
protocol P2 {}
205+
protocol P3: P1, P2 {}
206+
207+
// expected-warning@+1 {{@preconcurrency attribute on conformance to 'P3' has no effect}}
208+
@MainActor struct S: @preconcurrency P3 {}
209+
}
210+
211+
// rdar://137794903
212+
do {
213+
protocol P1 {}
214+
protocol P2 {
215+
func foo() // expected-note 2 {{mark the protocol requirement 'foo()' 'async' to allow actor-isolated conformances}}
216+
}
217+
protocol P3: P1, P2 {}
218+
219+
// OK, preconcurrency effectful because it is used by (implied) conformance
220+
// to inherited protocol 'P2'.
221+
@MainActor struct S1: @preconcurrency P3 {
222+
func foo() {}
223+
}
224+
// OK.
225+
@MainActor struct S2: @preconcurrency P2, P3 {
226+
func foo() {}
227+
}
228+
// OK.
229+
@MainActor struct S3: P3, @preconcurrency P2 {
230+
func foo() {}
231+
}
232+
233+
// Explicit conformances to inherited protocols do not contribute to whether
234+
// preconcurrency has effect on the conformance to the refined protocol, so
235+
// preconcurrency has no effect here.
236+
@MainActor struct S4: @preconcurrency P3, P2 {
237+
// expected-warning@-1:21 {{@preconcurrency attribute on conformance to 'P3' has no effect}}
238+
// expected-note@-2:45 {{add '@preconcurrency' to the 'P2' conformance to defer isolation checking to run time}}
239+
func foo() {}
240+
// expected-warning@-1 {{main actor-isolated instance method 'foo()' cannot be used to satisfy nonisolated requirement from protocol 'P2'}}
241+
// expected-note@-2 {{add 'nonisolated' to 'foo()' to make this instance method not isolated to the actor}}
242+
}
243+
@MainActor struct S5: P2, @preconcurrency P3 {
244+
// expected-warning@-1:21 {{@preconcurrency attribute on conformance to 'P3' has no effect}}
245+
// expected-note@-2:25 {{add '@preconcurrency' to the 'P2' conformance to defer isolation checking to run time}}
246+
func foo() {}
247+
// expected-warning@-1 {{main actor-isolated instance method 'foo()' cannot be used to satisfy nonisolated requirement from protocol 'P2'}}
248+
// expected-note@-2 {{add 'nonisolated' to 'foo()' to make this instance method not isolated to the actor}}
249+
}
250+
// expected-warning@+1 {{@preconcurrency attribute on conformance to 'P3' has no effect}}
251+
@MainActor struct S6: @preconcurrency P2, @preconcurrency P3 {
252+
func foo() {}
253+
}
254+
}
255+
do {
256+
protocol P1 {}
257+
protocol P2 {
258+
func foo()
259+
}
260+
protocol P3: P1, P2 {}
261+
protocol P4 {}
262+
263+
// OK, preconcurrency effectful because it is used by implied conformance to
264+
// inherited protocol 'P2'.
265+
@MainActor struct S1: P4, @preconcurrency P3 {
266+
func foo() {}
267+
}
268+
@MainActor struct S2: @preconcurrency P3, P4 {
269+
func foo() {}
270+
}
271+
272+
// Preconcurrency effectful for 'P3' only.
273+
@MainActor struct S3: @preconcurrency P3 & P4 {
274+
// expected-warning@-1:21 {{@preconcurrency attribute on conformance to 'P4' has no effect}}
275+
func foo() {}
276+
}
277+
}
278+
do {
279+
protocol P1 {}
280+
protocol P2 {
281+
func foo() // expected-note {{mark the protocol requirement 'foo()' 'async' to allow actor-isolated conformances}}
282+
}
283+
protocol P3: P1, P2 {}
284+
protocol P5: P3 {}
285+
protocol P6: P3 {}
286+
287+
// OK, preconcurrency effectful for both 'P5' and 'P6' because it is used
288+
// by implied conformance to mutually inherited protocol 'P2'.
289+
@MainActor struct S1: @preconcurrency P5 & P6 {
290+
func foo() {}
291+
}
292+
@MainActor struct S2: @preconcurrency P5, @preconcurrency P6 {
293+
func foo() {}
294+
}
295+
296+
// OK, preconcurrency effectful because it is used by implied conformance to
297+
// inherited protocol 'P2'.
298+
@MainActor struct S3: @preconcurrency P5, P6 {
299+
func foo() {}
300+
}
301+
@MainActor struct S4: P6, @preconcurrency P5 {
302+
// expected-warning@-1:21 {{@preconcurrency attribute on conformance to 'P5' has no effect}}
303+
func foo() {}
304+
// expected-warning@-1 {{main actor-isolated instance method 'foo()' cannot be used to satisfy nonisolated requirement from protocol 'P2'}}
305+
// expected-note@-2 {{add 'nonisolated' to 'foo()' to make this instance method not isolated to the actor}}
306+
}
307+
}
308+

0 commit comments

Comments
 (0)