Skip to content

Commit b4792a5

Browse files
authored
Merge pull request #73720 from DougGregor/preconcurrency-fixes-6.0
[6.0] Respect `@preconcurrency` everywhere we diagnose `Sendable` issues
2 parents db12dff + 6654567 commit b4792a5

20 files changed

+243
-111
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -692,9 +692,6 @@ ERROR(isolated_after_nonisolated, none,
692692
NOTE(nonisolated_blame, none, "after %1%2 %3, "
693693
"only non-isolated properties of 'self' can be accessed from "
694694
"%select{this init|a deinit}0", (bool, StringRef, StringRef, DeclName))
695-
ERROR(non_sendable_from_deinit,none,
696-
"cannot access %1 %2 with a non-sendable type %0 from non-isolated deinit",
697-
(Type, DescriptiveDeclKind, DeclName))
698695
ERROR(isolated_property_mutation_in_nonisolated_context,none,
699696
"actor-isolated %kind0 can not be %select{referenced|mutated}1 "
700697
"from a non-isolated context",

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5481,8 +5481,8 @@ ERROR(sendable_isolated_sync_function,none,
54815481
"%0 synchronous %kind1 cannot be marked as '@Sendable'",
54825482
(ActorIsolation, const ValueDecl *))
54835483
ERROR(nonsendable_instance_method,none,
5484-
"instance methods of non-Sendable types cannot be marked as '@Sendable'",
5485-
())
5484+
"instance method of non-Sendable type %0 cannot be marked as '@Sendable'",
5485+
(Type))
54865486
ERROR(concurrent_access_of_local_capture,none,
54875487
"%select{mutation of|reference to}0 captured %kind1 in "
54885488
"concurrently-executing code",
@@ -5575,9 +5575,9 @@ NOTE(shared_mutable_state_decl_note,none,
55755575
"isolate %0 to a global actor, or convert it to a 'let' constant and "
55765576
"conform it to 'Sendable'", (const ValueDecl *))
55775577
ERROR(shared_immutable_state_decl,none,
5578-
"%kind0 is not concurrency-safe because non-'Sendable' type %1 may have "
5578+
"%kind1 is not concurrency-safe because non-'Sendable' type %0 may have "
55795579
"shared mutable state",
5580-
(const ValueDecl *, Type))
5580+
(Type, const ValueDecl *))
55815581
NOTE(shared_immutable_state_decl_note,none,
55825582
"isolate %0 to a global actor, or conform %1 to 'Sendable'",
55835583
(const ValueDecl *, Type))
@@ -5757,6 +5757,10 @@ ERROR(nonisolated_wrapped_property,none,
57575757
"'nonisolated' is not supported on properties with property wrappers",
57585758
())
57595759

5760+
ERROR(non_sendable_from_deinit,none,
5761+
"cannot access %1 %2 with a non-sendable type %0 from non-isolated deinit",
5762+
(Type, DescriptiveDeclKind, DeclName))
5763+
57605764
ERROR(actor_instance_property_wrapper,none,
57615765
"%0 property in property wrapper type %1 cannot be isolated to "
57625766
"the actor instance; consider 'nonisolated'",

include/swift/Sema/Concurrency.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
namespace swift {
2424

25+
class DeclContext;
2526
class SourceFile;
2627
class NominalTypeDecl;
28+
class VarDecl;
2729

2830
/// If any of the imports in this source file was @preconcurrency but there were
2931
/// no diagnostics downgraded or suppressed due to that @preconcurrency, suggest
@@ -35,6 +37,13 @@ void diagnoseUnnecessaryPreconcurrencyImports(SourceFile &sf);
3537
bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
3638
bool applyModuleDefault = true);
3739

40+
/// Diagnose the use of an instance property of non-sendable type from an
41+
/// nonisolated deinitializer within an actor-isolated type.
42+
///
43+
/// \returns true iff a diagnostic was emitted for this reference.
44+
bool diagnoseNonSendableFromDeinit(
45+
SourceLoc refLoc, VarDecl *var, DeclContext *dc);
46+
3847
} // namespace swift
3948

4049
#endif

lib/SILOptimizer/Mandatory/FlowIsolation.cpp

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/AST/Expr.h"
1717
#include "swift/AST/ActorIsolation.h"
1818
#include "swift/AST/DiagnosticsSIL.h"
19+
#include "swift/Sema/Concurrency.h"
1920
#include "swift/SIL/ApplySite.h"
2021
#include "swift/SIL/BitDataflow.h"
2122
#include "swift/SIL/BasicBlockBits.h"
@@ -528,31 +529,17 @@ static bool onlyDeinitAccess(RefElementAddrInst *inst) {
528529
/// is happening in a deinit that uses flow-isolation.
529530
/// \returns true iff a diagnostic was emitted for this reference.
530531
static bool diagnoseNonSendableFromDeinit(RefElementAddrInst *inst) {
531-
VarDecl *var = inst->getField();
532-
Type ty = var->getTypeInContext();
533-
DeclContext *dc = inst->getFunction()->getDeclContext();
532+
auto dc = inst->getFunction()->getDeclContext();
534533

535-
// FIXME: we should emit diagnostics in other modes using:
536-
//
537-
// if (!shouldDiagnoseExistingDataRaces(dc))
538-
// return false;
539-
//
540-
// but until we decide how we want to handle isolated state from deinits,
541-
// we're going to limit the noise to complete mode for now.
534+
// For historical reasons, only diagnose this issue in strict mode.
542535
if (dc->getASTContext().LangOpts.StrictConcurrencyLevel
543-
!= StrictConcurrency::Complete)
544-
return false;
545-
546-
if (ty->isSendableType())
536+
!= StrictConcurrency::Complete)
547537
return false;
548538

549-
auto &diag = var->getASTContext().Diags;
550-
551-
diag.diagnose(inst->getLoc().getSourceLoc(), diag::non_sendable_from_deinit,
552-
ty, var->getDescriptiveKind(), var->getName())
553-
.warnUntilSwiftVersion(6);
554-
555-
return true;
539+
return swift::diagnoseNonSendableFromDeinit(
540+
inst->getLoc().getSourceLoc(),
541+
inst->getField(),
542+
dc);
556543
}
557544

558545
class OperandWorklist {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6918,10 +6918,13 @@ void AttributeChecker::visitSendableAttr(SendableAttr *attr) {
69186918
// Prevent Sendable Attr from being added to methods of non-sendable types
69196919
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(D)) {
69206920
if (auto selfDecl = funcDecl->getImplicitSelfDecl()) {
6921-
if (!selfDecl->getTypeInContext()->isSendableType()) {
6922-
diagnose(attr->getLocation(), diag::nonsendable_instance_method)
6923-
.warnUntilSwiftVersion(6);
6924-
}
6921+
diagnoseIfAnyNonSendableTypes(
6922+
selfDecl->getTypeInContext(),
6923+
SendableCheckContext(funcDecl),
6924+
Type(),
6925+
SourceLoc(),
6926+
attr->getLocation(),
6927+
diag::nonsendable_instance_method);
69256928
}
69266929
}
69276930
}
@@ -6945,13 +6948,16 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
69456948

69466949
// 'nonisolated' without '(unsafe)' is not allowed on non-Sendable variables.
69476950
auto type = var->getTypeInContext();
6948-
if (!attr->isUnsafe() && !type->hasError() &&
6949-
!type->isSendableType()) {
6950-
Ctx.Diags.diagnose(attr->getLocation(),
6951-
diag::nonisolated_non_sendable,
6952-
type)
6953-
.warnUntilSwiftVersion(6);
6954-
return;
6951+
if (!attr->isUnsafe() && !type->hasError()) {
6952+
bool diagnosed = diagnoseIfAnyNonSendableTypes(
6953+
type,
6954+
SendableCheckContext(dc),
6955+
Type(),
6956+
SourceLoc(),
6957+
attr->getLocation(),
6958+
diag::nonisolated_non_sendable);
6959+
if (diagnosed)
6960+
return;
69556961
}
69566962

69576963
if (auto nominal = dyn_cast<NominalTypeDecl>(dc)) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,6 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule,
511511
return true;
512512

513513
if (!var->isLet()) {
514-
ASTContext &ctx = var->getASTContext();
515514
// A mutable storage of a value type accessed from within the module is
516515
// okay.
517516
if (dyn_cast_or_null<StructDecl>(var->getDeclContext()->getAsDecl()) &&
@@ -841,16 +840,25 @@ SendableCheckContext::preconcurrencyBehavior(Decl *decl) const {
841840
return std::nullopt;
842841

843842
if (auto *nominal = dyn_cast<NominalTypeDecl>(decl)) {
844-
// Determine whether this nominal type is visible via a @preconcurrency
845-
// import.
846-
auto import = nominal->findImport(fromDC);
847-
auto sourceFile = fromDC->getParentSourceFile();
843+
ModuleDecl *importedModule = nullptr;
844+
if (nominal->getAttrs().hasAttribute<PreconcurrencyAttr>()) {
845+
// If the declaration itself has the @preconcurrency attribute,
846+
// respect it.
847+
importedModule = nominal->getParentModule();
848+
} else {
849+
// Determine whether this nominal type is visible via a @preconcurrency
850+
// import.
851+
auto import = nominal->findImport(fromDC);
852+
auto sourceFile = fromDC->getParentSourceFile();
848853

849-
if (!import || !import->options.contains(ImportFlags::Preconcurrency))
850-
return std::nullopt;
854+
if (!import || !import->options.contains(ImportFlags::Preconcurrency))
855+
return std::nullopt;
851856

852-
if (sourceFile)
853-
sourceFile->setImportUsedPreconcurrency(*import);
857+
if (sourceFile)
858+
sourceFile->setImportUsedPreconcurrency(*import);
859+
860+
importedModule = import->module.importedModule;
861+
}
854862

855863
// When the type is explicitly non-Sendable, @preconcurrency imports
856864
// downgrade the diagnostic to a warning in Swift 6.
@@ -859,7 +867,7 @@ SendableCheckContext::preconcurrencyBehavior(Decl *decl) const {
859867

860868
// When the type is implicitly non-Sendable, `@preconcurrency` suppresses
861869
// diagnostics until the imported module enables Swift 6.
862-
return import->module.importedModule->isConcurrencyChecked()
870+
return importedModule->isConcurrencyChecked()
863871
? DiagnosticBehavior::Warning
864872
: DiagnosticBehavior::Ignore;
865873
}
@@ -5011,13 +5019,17 @@ ActorIsolation ActorIsolationRequest::evaluate(
50115019
}
50125020
if (var->isLet()) {
50135021
auto type = var->getInterfaceType();
5014-
if (!type->isSendableType()) {
5015-
diagVar->diagnose(diag::shared_immutable_state_decl,
5016-
diagVar, type)
5017-
.warnUntilSwiftVersion(6);
5022+
bool diagnosed = diagnoseIfAnyNonSendableTypes(
5023+
type, SendableCheckContext(var->getDeclContext()),
5024+
/*inDerivedConformance=*/Type(), /*typeLoc=*/SourceLoc(),
5025+
/*diagnoseLoc=*/var->getLoc(),
5026+
diag::shared_immutable_state_decl, diagVar);
5027+
5028+
// If we diagnosed this 'let' as non-Sendable, tack on a note
5029+
// to suggest a course of action.
5030+
if (diagnosed)
50185031
diagVar->diagnose(diag::shared_immutable_state_decl_note,
50195032
diagVar, type);
5020-
}
50215033
} else {
50225034
diagVar->diagnose(diag::shared_mutable_state_decl, diagVar)
50235035
.warnUntilSwiftVersion(6);
@@ -5705,7 +5717,7 @@ static bool checkSendableInstanceStorage(
57055717
/*inDerivedConformance*/Type(), element->getLoc(),
57065718
[&](Type type, DiagnosticBehavior behavior) {
57075719
auto preconcurrency =
5708-
context.preconcurrencyBehavior(elementType->getAnyNominal());
5720+
context.preconcurrencyBehavior(type->getAnyNominal());
57095721
if (isImplicitSendableCheck(check)) {
57105722
// If this is for an externally-visible conformance, fail.
57115723
if (check == SendableCheck::ImplicitForExternallyVisible) {
@@ -5716,7 +5728,7 @@ static bool checkSendableInstanceStorage(
57165728
// If we are to ignore this diagnostic, just continue.
57175729
if (behavior == DiagnosticBehavior::Ignore ||
57185730
preconcurrency == DiagnosticBehavior::Ignore)
5719-
return false;
5731+
return true;
57205732

57215733
invalid = true;
57225734
return true;
@@ -6827,3 +6839,14 @@ ActorReferenceResult ActorReferenceResult::forReference(
68276839

68286840
return forEntersActor(declIsolation, options);
68296841
}
6842+
6843+
bool swift::diagnoseNonSendableFromDeinit(
6844+
SourceLoc refLoc, VarDecl *var, DeclContext *dc) {
6845+
return diagnoseIfAnyNonSendableTypes(var->getTypeInContext(),
6846+
SendableCheckContext(dc),
6847+
Type(),
6848+
SourceLoc(),
6849+
refLoc,
6850+
diag::non_sendable_from_deinit,
6851+
var->getDescriptiveKind(), var->getName());
6852+
}

lib/Sema/TypeCheckConcurrency.h

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -436,20 +436,64 @@ bool diagnoseNonSendableTypes(
436436
Diag<Type, DiagArgs...> diag,
437437
typename detail::Identity<DiagArgs>::type ...diagArgs) {
438438

439-
ASTContext &ctx = fromContext.fromDC->getASTContext();
440-
return diagnoseNonSendableTypes(
441-
type, fromContext, derivedConformance, typeLoc,
442-
[&](Type specificType, DiagnosticBehavior behavior) {
443-
auto preconcurrency =
444-
fromContext.preconcurrencyBehavior(type->getAnyNominal());
439+
ASTContext &ctx = fromContext.fromDC->getASTContext();
440+
return diagnoseNonSendableTypes(
441+
type, fromContext, derivedConformance, typeLoc,
442+
[&](Type specificType, DiagnosticBehavior behavior) {
443+
auto preconcurrency =
444+
fromContext.preconcurrencyBehavior(specificType->getAnyNominal());
445+
446+
ctx.Diags.diagnose(diagnoseLoc, diag, type, diagArgs...)
447+
.limitBehaviorUntilSwiftVersion(behavior, 6)
448+
.limitBehaviorIf(preconcurrency);
449+
450+
return (behavior == DiagnosticBehavior::Ignore ||
451+
preconcurrency == DiagnosticBehavior::Ignore);
452+
});
453+
}
454+
455+
/// Emit a diagnostic if there are any non-Sendable types for which
456+
/// the Sendable diagnostic wasn't suppressed. This diagnostic will
457+
/// only be emitted once, but there might be additional notes for the
458+
/// various occurrences of Sendable types.
459+
///
460+
/// \param typeLoc is the source location of the type being diagnosed
461+
///
462+
/// \param diagnoseLoc is the source location at which the main diagnostic should
463+
/// be reported, which can differ from typeLoc
464+
///
465+
/// \returns \c true if any diagnostics.
466+
template<typename ...DiagArgs>
467+
bool diagnoseIfAnyNonSendableTypes(
468+
Type type, SendableCheckContext fromContext,
469+
Type derivedConformance,
470+
SourceLoc typeLoc, SourceLoc diagnoseLoc,
471+
Diag<Type, DiagArgs...> diag,
472+
typename detail::Identity<DiagArgs>::type ...diagArgs) {
473+
474+
ASTContext &ctx = fromContext.fromDC->getASTContext();
475+
bool diagnosed = false;
476+
diagnoseNonSendableTypes(
477+
type, fromContext, derivedConformance, typeLoc,
478+
[&](Type specificType, DiagnosticBehavior behavior) {
479+
auto preconcurrency =
480+
fromContext.preconcurrencyBehavior(specificType->getAnyNominal());
445481

482+
if (behavior == DiagnosticBehavior::Ignore ||
483+
preconcurrency == DiagnosticBehavior::Ignore)
484+
return true;
485+
486+
if (!diagnosed) {
446487
ctx.Diags.diagnose(diagnoseLoc, diag, type, diagArgs...)
447488
.limitBehaviorUntilSwiftVersion(behavior, 6)
448489
.limitBehaviorIf(preconcurrency);
490+
diagnosed = true;
491+
}
492+
493+
return false;
494+
});
449495

450-
return (behavior == DiagnosticBehavior::Ignore ||
451-
preconcurrency == DiagnosticBehavior::Ignore);
452-
});
496+
return diagnosed;
453497
}
454498

455499
/// Diagnose any non-Sendable types that occur within the given type, using

test/Concurrency/Inputs/NonStrictModule.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
public struct NonStrictStruct { }
22

33
open class NonStrictClass {
4+
public init() {}
45
open func send(_ body: @Sendable () -> Void) {}
56
open func dontSend(_ body: () -> Void) {}
67
}

test/Concurrency/Inputs/StrictModule.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
public struct StrictStruct: Hashable { }
1+
public struct StrictStruct: Hashable {
2+
public init() {}
3+
}
24

35
open class StrictClass {
46
open func send(_ body: @Sendable () -> Void) {}

test/Concurrency/concurrency_warnings.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// REQUIRES: concurrency
55
// REQUIRES: asserts
66

7-
class GlobalCounter {
7+
class GlobalCounter { // expected-note{{class 'GlobalCounter' does not conform to the 'Sendable' protocol}}
88
var counter: Int = 0
99
}
1010

test/Concurrency/concurrent_value_checking.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func testUnsafeSendableInAsync() async {
215215
// ----------------------------------------------------------------------
216216
// Sendable restriction on key paths.
217217
// ----------------------------------------------------------------------
218-
class NC: Hashable { // expected-note 2{{class 'NC' does not conform to the 'Sendable' protocol}}
218+
class NC: Hashable { // expected-note 3{{class 'NC' does not conform to the 'Sendable' protocol}}
219219
func hash(into: inout Hasher) { }
220220
static func==(_: NC, _: NC) -> Bool { true }
221221
}

test/Concurrency/flow_isolation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func takeNonSendable(_ ns: NonSendableType) {}
1515
@available(SwiftStdlib 5.1, *)
1616
func takeSendable(_ s: SendableType) {}
1717

18-
class NonSendableType {
18+
class NonSendableType { // expected-note *{{class 'NonSendableType' does not conform to the 'Sendable' protocol}}
1919
var x: Int = 0
2020
func f() {}
2121
}

test/Concurrency/global_variables.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ final class TestSendable: Sendable {
2323
init() {}
2424
}
2525

26-
final class TestNonsendable {
26+
final class TestNonsendable { // expected-note 3{{class 'TestNonsendable' does not conform to the 'Sendable' protocol}}
2727
init() {}
2828
}
2929

0 commit comments

Comments
 (0)