Skip to content

Commit 640042f

Browse files
committed
Suggest @preconcurrency on conformances it could help
When diagnosing a case where an actor-isolated witness cannot satisfy a non-isolated requirement, also suggest that the conformance could be annotated with `@preconcurrency`.
1 parent 31337dd commit 640042f

13 files changed

+64
-16
lines changed

include/swift/AST/DiagnosticsSema.def

+2
Original file line numberDiff line numberDiff line change
@@ -2655,6 +2655,8 @@ WARNING(add_predates_concurrency_import,none,
26552655
"%select{| as warnings}0", (bool, Identifier))
26562656
WARNING(remove_predates_concurrency_import,none,
26572657
"'@preconcurrency' attribute on module %0 has no effect", (Identifier))
2658+
NOTE(add_preconcurrency_to_conformance,none,
2659+
"add '@preconcurrency' to the %0 conformance to suppress isolation-related diagnostics", (DeclName))
26582660
WARNING(remove_public_import,none,
26592661
"public import of %0 was not used in public declarations or inlinable code",
26602662
(const ModuleDecl *))

lib/AST/ConformanceLookupTable.cpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -969,10 +969,11 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
969969
assert(!isa<ProtocolDecl>(conformingDC->getSelfNominalTypeDecl()));
970970
Type conformingType = conformingDC->getSelfInterfaceType();
971971

972-
SourceLoc conformanceLoc
973-
= conformingNominal == conformingDC
974-
? conformingNominal->getLoc()
975-
: cast<ExtensionDecl>(conformingDC)->getLoc();
972+
SourceLoc conformanceLoc =
973+
entry->getLoc().isValid() ? entry->getLoc()
974+
: (conformingNominal == conformingDC
975+
? conformingNominal->getLoc()
976+
: cast<ExtensionDecl>(conformingDC)->getLoc());
976977

977978
NormalProtocolConformance *implyingConf = nullptr;
978979
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {

lib/Sema/TypeCheckProtocol.cpp

+23-8
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,7 @@ static void diagnoseConformanceImpliedByConditionalConformance(
20632063
auto proto = conformance->getProtocol();
20642064
Type protoType = proto->getDeclaredInterfaceType();
20652065
auto implyingProto = implyingConf->getProtocol()->getDeclaredInterfaceType();
2066-
auto loc = implyingConf->getLoc();
2066+
auto loc = extractNearestSourceLoc(implyingConf->getDeclContext());
20672067
Diags.diagnose(loc, diag::conditional_conformances_cannot_imply_conformances,
20682068
conformance->getType(), implyingProto, protoType);
20692069

@@ -3297,13 +3297,28 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
32973297
missingOptions -= MissingFlags::WitnessDistributed;
32983298
}
32993299

3300-
// One way to address the issue is to make the witness function nonisolated.
3301-
if ((isa<AbstractFunctionDecl>(witness) || isa<SubscriptDecl>(witness)) &&
3302-
!hasExplicitGlobalActorAttr(witness) &&
3303-
!isDistributedDecl(requirement) &&
3304-
!isDistributedDecl(witness)) {
3305-
witness->diagnose(diag::note_add_nonisolated_to_decl, witness)
3306-
.fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated ");
3300+
// If 'nonisolated' or 'preconcurrency' might help us, provide those as
3301+
// options.
3302+
if (!isDistributedDecl(requirement) && !isDistributedDecl(witness)) {
3303+
// One way to address the issue is to make the witness function nonisolated.
3304+
if ((isa<AbstractFunctionDecl>(witness) || isa<SubscriptDecl>(witness)) &&
3305+
!hasExplicitGlobalActorAttr(witness)) {
3306+
witness->diagnose(diag::note_add_nonisolated_to_decl, witness)
3307+
.fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated ");
3308+
}
3309+
3310+
// Another way to address the issue is to mark the conformance as
3311+
// "preconcurrency".
3312+
if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit &&
3313+
!Conformance->isPreconcurrency() &&
3314+
!suggestedPreconcurrency &&
3315+
!requirementIsolation.isActorIsolated()) {
3316+
Context.Diags.diagnose(Conformance->getLoc(),
3317+
diag::add_preconcurrency_to_conformance,
3318+
Proto->getName())
3319+
.fixItInsert(Conformance->getLoc(), "@preconcurrency ");
3320+
suggestedPreconcurrency = true;
3321+
}
33073322
}
33083323

33093324
// If there are remaining options, they are missing async/throws on the

lib/Sema/TypeCheckProtocol.h

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ enum class ResolveWitnessResult {
112112
/// This helper class handles most of the details of checking whether a
113113
/// given type (\c Adoptee) conforms to a protocol (\c Proto).
114114
class ConformanceChecker : public WitnessChecker {
115+
/// Whether we already suggested adding `@preconcurrency`.
116+
bool suggestedPreconcurrency = false;
117+
115118
public:
116119
NormalProtocolConformance *Conformance;
117120
SourceLoc Loc;

test/APINotes/versioned-objc.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import APINotesFrameworkTest
99

1010
// CHECK-DIAGS-5-NOT: versioned-objc.swift:[[@LINE-1]]:
1111
class ProtoWithVersionedUnavailableMemberImpl: ProtoWithVersionedUnavailableMember {
12-
// CHECK-DIAGS-4: versioned-objc.swift:[[@LINE-1]]:7: error: type 'ProtoWithVersionedUnavailableMemberImpl' cannot conform to protocol 'ProtoWithVersionedUnavailableMember' because it has requirements that cannot be satisfied
12+
// CHECK-DIAGS-4: versioned-objc.swift:[[@LINE-1]]:48: error: type 'ProtoWithVersionedUnavailableMemberImpl' cannot conform to protocol 'ProtoWithVersionedUnavailableMember' because it has requirements that cannot be satisfied
1313
func requirement() -> Any? { return nil }
1414
}
1515

test/Concurrency/actor_isolation.swift

+2
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,8 @@ protocol NonisolatedProtocol {
15861586
}
15871587

15881588
actor ActorWithNonSendableLet: NonisolatedProtocol {
1589+
// expected-note@-1{{add '@preconcurrency' to the 'NonisolatedProtocol' conformance to suppress isolation-related diagnostics}}{{32-32=@preconcurrency }}
1590+
15891591
// expected-warning@+1 {{actor-isolated property 'ns' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode}}
15901592
let ns = NonSendable()
15911593
}

test/Concurrency/global_actor_inference.swift

+2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ protocol Interface {
117117

118118
@MainActor
119119
class Object: Interface {
120+
// expected-note@-1{{add '@preconcurrency' to the 'Interface' conformance to suppress isolation-related diagnostics}}{{15-15=@preconcurrency }}
121+
120122
var baz: Int = 42 // expected-warning{{main actor-isolated property 'baz' cannot be used to satisfy nonisolated protocol requirement}}
121123
}
122124

test/Concurrency/preconcurrency_conformances.swift

+8
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,21 @@ extension MyActor : @preconcurrency TestSendability {
9191

9292
protocol Initializable {
9393
init()
94+
// expected-note@-1{{mark the protocol requirement 'init()' 'async' to allow actor-isolated conformances}}
9495
}
9596

9697
final class K : @preconcurrency Initializable {
9798
// expected-warning@-1 {{@preconcurrency attribute on conformance to 'Initializable' has no effect}}
9899
init() {} // Ok
99100
}
100101

102+
@MainActor
103+
final class MainActorK: Initializable {
104+
// expected-note@-1{{add '@preconcurrency' to the 'Initializable' conformance to suppress isolation-related diagnostics}}{{25-25=@preconcurrency }}
105+
init() { } // expected-warning{{main actor-isolated initializer 'init()' cannot be used to satisfy nonisolated protocol requirement}}
106+
// expected-note@-1{{add 'nonisolated' to 'init()' to make this initializer not isolated to the actor}}
107+
}
108+
101109
protocol WithAssoc {
102110
associatedtype T
103111
func test() -> T

test/Concurrency/predates_concurrency.swift

+3
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ protocol NotIsolated {
230230
}
231231

232232
extension MainActorPreconcurrency: NotIsolated {
233+
// expected-complete-note@-1{{add '@preconcurrency' to the 'NotIsolated' conformance to suppress isolation-related diagnostics}}{{36-36=@preconcurrency }}
234+
// expected-complete-tns-note@-2{{add '@preconcurrency' to the 'NotIsolated' conformance to suppress isolation-related diagnostics}}{{36-36=@preconcurrency }}
235+
233236
func requirement() {}
234237
// expected-complete-tns-warning@-1 {{main actor-isolated instance method 'requirement()' cannot be used to satisfy nonisolated protocol requirement}}
235238
// expected-complete-tns-note@-2 {{add 'nonisolated' to 'requirement()' to make this instance method not isolated to the actor}}

test/Distributed/distributed_protocol_isolation.swift

+6
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ protocol StrictlyLocal {
106106
}
107107

108108
distributed actor Nope1_StrictlyLocal: StrictlyLocal {
109+
// expected-note@-1{{add '@preconcurrency' to the 'StrictlyLocal' conformance to suppress isolation-related diagnostics}}
110+
109111
func local() {}
110112
// expected-error@-1{{distributed actor-isolated instance method 'local()' cannot be used to satisfy nonisolated protocol requirement}}
111113
// expected-note@-2{{add 'nonisolated' to 'local()' to make this instance method not isolated to the actor}}
@@ -157,6 +159,8 @@ actor LocalOK_ImplicitlyThrowsAsync_AsyncThrowsAll: AsyncThrowsAll {
157159
}
158160

159161
distributed actor Nope1_AsyncThrowsAll: AsyncThrowsAll {
162+
// expected-note@-1{{add '@preconcurrency' to the 'AsyncThrowsAll' conformance to suppress isolation-related diagnostics}}
163+
160164
func maybe(param: String, int: Int) async throws -> Int { 111 }
161165
// expected-error@-1{{distributed actor-isolated instance method 'maybe(param:int:)' cannot be used to satisfy nonisolated protocol requirement}}
162166
// expected-note@-2{{add 'nonisolated' to 'maybe(param:int:)' to make this instance method not isolated to the actor}}
@@ -202,6 +206,8 @@ func test_watching_A(a: A_TerminationWatchingA) async throws {
202206
}
203207

204208
distributed actor DA_TerminationWatchingA: TerminationWatchingA {
209+
// expected-note@-1{{add '@preconcurrency' to the 'TerminationWatchingA' conformance to suppress isolation-related diagnostics}}
210+
205211
func terminated(a: String) { }
206212
// expected-error@-1{{distributed actor-isolated instance method 'terminated(a:)' cannot be used to satisfy nonisolated protocol requirement}}
207213
// expected-note@-2{{add 'nonisolated' to 'terminated(a:)' to make this instance method not isolated to the actor}}

test/decl/class/actor/conformance.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ protocol SyncProtocol {
3636

3737

3838
actor OtherActor: SyncProtocol {
39+
// expected-note@-1{{add '@preconcurrency' to the 'SyncProtocol' conformance to suppress isolation-related diagnostics}}{{19-19=@preconcurrency }}
40+
3941
var propertyB: Int = 17
4042
// expected-error@-1{{actor-isolated property 'propertyB' cannot be used to satisfy nonisolated protocol requirement}}
4143

@@ -95,4 +97,4 @@ actor A2: Initializers {
9597
init(int: Int) { }
9698

9799
func withBells() async -> A2 { self }
98-
}
100+
}

test/decl/class/actor/global_actor_conformance.swift

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ protocol P2 {
3030
}
3131

3232
class C1 : P1, P2 {
33+
// expected-note@-1{{add '@preconcurrency' to the 'P1' conformance to suppress isolation-related diagnostics}}
34+
3335
typealias Assoc = String
3436

3537
func method1() { }
@@ -52,6 +54,8 @@ protocol NonIsolatedRequirement {
5254
@MainActor class OnMain {}
5355

5456
extension OnMain: NonIsolatedRequirement {
57+
// expected-note@-1{{add '@preconcurrency' to the 'NonIsolatedRequirement' conformance to suppress isolation-related diagnostics}}
58+
5559
// expected-warning@+2 {{main actor-isolated instance method 'requirement()' cannot be used to satisfy nonisolated protocol requirement}}
5660
// expected-note@+1 {{add 'nonisolated' to 'requirement()' to make this instance method not isolated to the actor}}
5761
func requirement() {}

test/decl/protocol/req/missing_conformance.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ struct CountSteps1<T> : Collection {
144144
subscript(i: Int) -> Int { return i }
145145
}
146146

147-
extension CountSteps1 // expected-error {{type 'CountSteps1<T>' does not conform to protocol 'RandomAccessCollection'}}
147+
extension CountSteps1 // expected-error {{type 'CountSteps1<T>' does not conform to protocol 'BidirectionalCollection'}}
148148
// expected-error@-1 {{conditional conformance of type 'CountSteps1<T>' to protocol 'RandomAccessCollection' does not imply conformance to inherited protocol 'BidirectionalCollection'}}
149149
// expected-note@-2 {{did you mean to explicitly state the conformance like 'extension CountSteps1: BidirectionalCollection where ...'?}}
150-
// expected-error@-3 {{type 'CountSteps1<T>' does not conform to protocol 'BidirectionalCollection'}}
150+
// expected-error@+1 {{type 'CountSteps1<T>' does not conform to protocol 'RandomAccessCollection'}}
151151
: RandomAccessCollection
152152
where T : Equatable
153153
{

0 commit comments

Comments
 (0)