From cc357f4f32402769a25d46c3d12129aea108ffbc Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 20 Jun 2025 11:05:26 -0700 Subject: [PATCH] Add Feature: NonescapableAccessorOnTrivial To guard the new UnsafeMutablePointer.mutableSpan APIs. This allows older compilers to ignore the new APIs. Otherwise, the type checker will crash on the synthesized _read accessor for a non-Escapable type: error: cannot infer lifetime dependence on the '_read' accessor because 'self' is BitwiseCopyable, specify '@lifetime(borrow self)' I don't know why the _read is synthesized in these cases, but apparently it's always been that way. Fixes: rdar://153773093 ([nonescapable] add a compiler feature to guard ~Escapable accessors when self is trivial) --- include/swift/AST/Types.h | 8 +++++++ include/swift/Basic/Features.def | 3 +++ lib/AST/ConformanceLookup.cpp | 18 +++++++++++++++ lib/AST/FeatureSet.cpp | 23 ++++++++++++++----- .../Inputs/lifetime_dependence.swift | 18 +++++++++++++++ .../lifetime_dependence_test.swift | 11 +++++++++ 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index d98b0ed72bb99..29e3e7e48bb43 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -700,6 +700,14 @@ class alignas(1 << TypeAlignInBits) TypeBase /// Returns true if this contextual type is (Escapable && !isNoEscape). bool mayEscape() { return !isNoEscape() && isEscapable(); } + /// Returns true if this contextual type satisfies a conformance to + /// BitwiseCopyable. + bool isBitwiseCopyable(); + + /// Returns true if this type satisfies a conformance to BitwiseCopyable in + /// the given generic signature. + bool isBitwiseCopyable(GenericSignature sig); + /// Are values of this type essentially just class references, /// possibly with some sort of additional information? /// diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 0b44270e1ed18..9d4d667a4d319 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -530,6 +530,9 @@ EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false) /// Enable @_lifetime attribute SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true) +/// Enable UnsafeMutablePointer.mutableSpan +EXPERIMENTAL_FEATURE(NonescapableAccessorOnTrivial, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ConformanceLookup.cpp b/lib/AST/ConformanceLookup.cpp index 1a3db12e18fa0..f8b2232c8e5fc 100644 --- a/lib/AST/ConformanceLookup.cpp +++ b/lib/AST/ConformanceLookup.cpp @@ -963,3 +963,21 @@ bool TypeBase::isEscapable(GenericSignature sig) { } return contextTy->isEscapable(); } + +bool TypeBase::isBitwiseCopyable() { + auto &ctx = getASTContext(); + auto *bitwiseCopyableProtocol = + ctx.getProtocol(KnownProtocolKind::BitwiseCopyable); + if (!bitwiseCopyableProtocol) { + return false; + } + return (bool)checkConformance(this, bitwiseCopyableProtocol); +} + +bool TypeBase::isBitwiseCopyable(GenericSignature sig) { + Type contextTy = this; + if (sig) { + contextTy = sig.getGenericEnvironment()->mapTypeIntoContext(contextTy); + } + return contextTy->isBitwiseCopyable(); +} diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 68c5af466077a..b9049d68a76b1 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -331,14 +331,25 @@ static bool usesFeatureLifetimeDependenceMutableAccessors(Decl *decl) { return false; } auto var = cast(decl); - if (!var->isGetterMutating()) { + return var->isGetterMutating() && !var->getTypeInContext()->isEscapable(); +} + +static bool usesFeatureNonescapableAccessorOnTrivial(Decl *decl) { + if (!isa(decl)) { return false; } - if (auto dc = var->getDeclContext()) { - if (auto nominal = dc->getSelfNominalTypeDecl()) { - auto sig = nominal->getGenericSignature(); - return !var->getInterfaceType()->isEscapable(sig); - } + auto var = cast(decl); + if (!var->hasParsedAccessors()) { + return false; + } + // Check for properties that are both non-Copyable and non-Escapable + // (MutableSpan). + if (var->getTypeInContext()->isNoncopyable() + && !var->getTypeInContext()->isEscapable()) { + auto selfTy = var->getDeclContext()->getSelfTypeInContext(); + // Consider 'self' trivial if it is BitwiseCopyable and Escapable + // (UnsafeMutableBufferPointer). + return selfTy->isBitwiseCopyable() && selfTy->isEscapable(); } return false; } diff --git a/test/ModuleInterface/Inputs/lifetime_dependence.swift b/test/ModuleInterface/Inputs/lifetime_dependence.swift index eb174ab09932d..15095a4861234 100644 --- a/test/ModuleInterface/Inputs/lifetime_dependence.swift +++ b/test/ModuleInterface/Inputs/lifetime_dependence.swift @@ -77,3 +77,21 @@ extension Container { } } } + +// Test feature guard: NonescapableAccessorOnTrivial +extension UnsafeMutableBufferPointer where Element: ~Copyable { + public var span: Span { + @lifetime(borrow self) + @_alwaysEmitIntoClient + get { + unsafe Span(_unsafeElements: self) + } + } + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + @_alwaysEmitIntoClient + get { + unsafe MutableSpan(_unsafeElements: self) + } + } +} diff --git a/test/ModuleInterface/lifetime_dependence_test.swift b/test/ModuleInterface/lifetime_dependence_test.swift index b705ee4cd4480..6721af476ba1a 100644 --- a/test/ModuleInterface/lifetime_dependence_test.swift +++ b/test/ModuleInterface/lifetime_dependence_test.swift @@ -2,6 +2,7 @@ // RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -emit-module \ // RUN: -enable-experimental-feature LifetimeDependence \ +// RUN: -suppress-warnings \ // RUN: -o %t/lifetime_dependence.swiftmodule \ // RUN: -emit-module-interface-path %t/lifetime_dependence.swiftinterface \ // RUN: %S/Inputs/lifetime_dependence.swift @@ -41,3 +42,13 @@ import lifetime_dependence // CHECK: extension lifetime_dependence.Container { // CHECK-NEXT: #if compiler(>=5.3) && $NonescapableTypes && $LifetimeDependence // CHECK-NEXT: public var storage: lifetime_dependence.BufferView { + +// CHECK-LABEL: extension Swift.UnsafeMutableBufferPointer where Element : ~Copyable { +// CHECK: #if compiler(>=5.3) && $LifetimeDependence +// CHECK: public var span: Swift.Span { +// CHECK: @lifetime(borrow self) +// CHECK: @_alwaysEmitIntoClient get { +// CHECK: #if compiler(>=5.3) && $LifetimeDependence && $NonescapableAccessorOnTrivial +// CHECK: public var mutableSpan: Swift.MutableSpan { +// CHECK: @lifetime(borrow self) +// CHECK: @_alwaysEmitIntoClient get {