Skip to content

Add Feature: NonescapableAccessorOnTrivial #82380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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?
///
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions lib/AST/ConformanceLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
23 changes: 17 additions & 6 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,25 @@ static bool usesFeatureLifetimeDependenceMutableAccessors(Decl *decl) {
return false;
}
auto var = cast<VarDecl>(decl);
if (!var->isGetterMutating()) {
return var->isGetterMutating() && !var->getTypeInContext()->isEscapable();
}

static bool usesFeatureNonescapableAccessorOnTrivial(Decl *decl) {
if (!isa<VarDecl>(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<VarDecl>(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;
}
Expand Down
18 changes: 18 additions & 0 deletions test/ModuleInterface/Inputs/lifetime_dependence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,21 @@ extension Container {
}
}
}

// Test feature guard: NonescapableAccessorOnTrivial
extension UnsafeMutableBufferPointer where Element: ~Copyable {
public var span: Span<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
get {
unsafe Span(_unsafeElements: self)
}
}
public var mutableSpan: MutableSpan<Element> {
@lifetime(borrow self)
@_alwaysEmitIntoClient
get {
unsafe MutableSpan(_unsafeElements: self)
}
}
}
11 changes: 11 additions & 0 deletions test/ModuleInterface/lifetime_dependence_test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Element> {
// CHECK: @lifetime(borrow self)
// CHECK: @_alwaysEmitIntoClient get {
// CHECK: #if compiler(>=5.3) && $LifetimeDependence && $NonescapableAccessorOnTrivial
// CHECK: public var mutableSpan: Swift.MutableSpan<Element> {
// CHECK: @lifetime(borrow self)
// CHECK: @_alwaysEmitIntoClient get {