Skip to content

[Swiftify] Update availability for CxxSpan<->Span, fix lifetimebound on parameters with reference type #81634

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
May 22, 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
7 changes: 4 additions & 3 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9268,9 +9268,10 @@ void ClangImporter::Implementation::swiftify(FuncDecl *MappedDecl) {
paramHasLifetimeInfo = true;
}
if (clangParam->hasAttr<clang::LifetimeBoundAttr>()) {
printer.printLifetimeboundReturn(
index, !paramHasBoundsInfo &&
swiftParamTy->isEscapable());
// If this parameter has bounds info we will tranform it into a Span,
// so then it will no longer be Escapable.
bool willBeEscapable = swiftParamTy->isEscapable() && !paramHasBoundsInfo;
printer.printLifetimeboundReturn(index, willBeEscapable);
paramHasLifetimeInfo = true;
returnHasLifetimeInfo = true;
}
Expand Down
24 changes: 20 additions & 4 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,18 @@ func setLifetimeDependencies(
}
}

func isInout(_ type: TypeSyntax) -> Bool {
guard let attr = type.as(AttributedTypeSyntax.self) else {
return false
}
return attr.specifiers.contains(where: { e in
guard let simpleSpec = e.as(SimpleTypeSpecifierSyntax.self) else {
return false
}
return simpleSpec.specifier.text == "inout"
})
}

func getReturnLifetimeAttribute(
_ funcDecl: FunctionDeclSyntax,
_ dependencies: [SwiftifyExpr: [LifetimeDependence]]
Expand All @@ -1314,10 +1326,14 @@ func getReturnLifetimeAttribute(
for dependence in returnDependencies {
switch dependence.type {
case .borrow:
args.append(
LabeledExprSyntax(
expression:
DeclReferenceExprSyntax(baseName: TokenSyntax("borrow"))))
if isInout(getSwiftifyExprType(funcDecl, dependence.dependsOn)) {
args.append(LabeledExprSyntax(expression: ExprSyntax("&")))
} else {
args.append(
LabeledExprSyntax(
expression:
DeclReferenceExprSyntax(baseName: TokenSyntax("borrow"))))
}
case .copy:
args.append(
LabeledExprSyntax(
Expand Down
8 changes: 4 additions & 4 deletions stdlib/public/Cxx/CxxSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extension CxxSpan {
unsafe self.init(unsafeMutableBufferPointer.baseAddress!, Size(unsafeMutableBufferPointer.count))
}

@available(SwiftStdlib 6.2, *)
@available(SwiftCompatibilitySpan 5.0, *)
@inlinable
@unsafe
public init(_ span: Span<Element>) {
Expand All @@ -99,7 +99,7 @@ extension CxxSpan {
}
}

@available(SwiftStdlib 6.2, *)
@available(SwiftCompatibilitySpan 5.0, *)
extension Span {
@_alwaysEmitIntoClient
@unsafe
Expand All @@ -115,7 +115,7 @@ extension Span {
}
}

@available(SwiftStdlib 6.2, *)
@available(SwiftCompatibilitySpan 5.0, *)
extension MutableSpan {
@_alwaysEmitIntoClient
@unsafe
Expand Down Expand Up @@ -151,7 +151,7 @@ extension CxxMutableSpan {
unsafe self.init(unsafeMutableBufferPointer.baseAddress!, Size(unsafeMutableBufferPointer.count))
}

@available(SwiftStdlib 6.2, *)
@available(SwiftCompatibilitySpan 5.0, *)
@inlinable
@unsafe
public init(_ span: consuming MutableSpan<Element>) {
Expand Down
101 changes: 99 additions & 2 deletions test/Interop/Cxx/stdlib/std-span-interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// RUN: %target-swift-ide-test -plugin-path %swift-plugin-dir -I %S/Inputs -enable-experimental-feature SafeInteropWrappers -print-module -module-to-print=StdSpan -source-filename=x -enable-experimental-cxx-interop -Xcc -std=c++20 -module-cache-path %t > %t/interface.swift
// RUN: %FileCheck %s < %t/interface.swift

// Make sure we trigger typechecking and SIL diagnostics
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs -enable-experimental-feature SafeInteropWrappers -enable-experimental-feature LifetimeDependence -cxx-interoperability-mode=default -strict-memory-safety -warnings-as-errors -Xcc -std=c++20 %s

// REQUIRES: swift_feature_SafeInteropWrappers
// REQUIRES: swift_feature_LifetimeDependence

// FIXME swift-ci linux tests do not support std::span
// UNSUPPORTED: OS=linux-gnu, OS=linux-android, OS=linux-androideabi
Expand Down Expand Up @@ -54,7 +58,7 @@ import CxxStdlib

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(borrow v)
// CHECK-NEXT: @lifetime(&v)
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func FuncWithMutableSafeWrapper3(_ v: inout VecOfInt) -> MutableSpan<CInt>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
Expand All @@ -65,7 +69,7 @@ import CxxStdlib

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(borrow v)
// CHECK-NEXT: @lifetime(&v)
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func MixedFuncWithMutableSafeWrapper2(_ v: inout VecOfInt, _ len: Int32) -> MutableSpan<Int32>

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
Expand Down Expand Up @@ -132,3 +136,96 @@ import CxxStdlib

// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
// CHECK-NEXT: @_alwaysEmitIntoClient public func mixedFuncWithSafeWrapper7(_ p: UnsafeBufferPointer<Int32>) -> ConstSpanOfInt

func callMethodWithSafeWrapper(_ x: inout X, s: Span<CInt>) {
x.methodWithSafeWrapper(s)
}

func callFooBar(_ x: inout SpanWithoutTypeAlias, _ s: ConstSpanOfInt) {
let _: Span<CInt> = x.bar()
unsafe x.foo(s)
}

@lifetime(span: copy span)
func callFuncWithMutableSafeWrapper(_ span: inout MutableSpan<CInt>, ) {
FuncWithMutableSafeWrapper(&span)
}

@lifetime(span: copy span)
func callFuncWithMutableSafeWrapper2(_ span: inout MutableSpan<CInt>, ) {
let _: MutableSpan<CInt> = FuncWithMutableSafeWrapper2(&span)
}

@lifetime(span: copy span)
func callMixedFuncWithMutableSafeWrapper1(_ span: inout MutableSpan<CInt>, ) {
let _: MutableSpan<CInt> = MixedFuncWithMutableSafeWrapper1(&span)
}

func MixedFuncWithMutableSafeWrapper2(_ v: VecOfInt) {
var v2 = v
let _ = MixedFuncWithMutableSafeWrapper2(&v2, 37)
}

@lifetime(span: copy span)
func callMixedFuncWithMutableSafeWrapper3(_ span: inout MutableSpan<CInt>, _ p: UnsafeMutableBufferPointer<CInt>) {
unsafe MixedFuncWithMutableSafeWrapper3(&span, p)
}

@lifetime(span1: copy span2)
@lifetime(span2: copy span2)
func callMixedFuncWithMutableSafeWrapper4(_ span1: inout MutableSpan<CInt>, _ span2: inout MutableSpan<CInt>) {
MixedFuncWithMutableSafeWrapper4(&span1, &span2)
}

@lifetime(span: copy span)
func callMixedFuncWithMutableSafeWrapper5(_ span: inout MutableSpan<CInt>, _ s: SpanOfInt) {
unsafe MixedFuncWithMutableSafeWrapper5(s, &span)
}

func callMixedFuncWithMutableSafeWrapper6(_ s: SpanOfInt, _ p: UnsafeMutableBufferPointer<CInt>) {
unsafe MixedFuncWithMutableSafeWrapper6(s, p)
}

func callMixedFuncWithMutableSafeWrapper7(_ p: UnsafeMutableBufferPointer<CInt>) {
let _ = unsafe MixedFuncWithMutableSafeWrapper7(p)
}

func callFuncWithSafeWrapper(_ s: Span<CInt>) {
funcWithSafeWrapper(s)
}

func callFuncWithSafeWrapper2(_ s: Span<CInt>) {
let _ = funcWithSafeWrapper2(s)
}

func callFuncWithSafeWrapper3(_ v: borrowing VecOfInt) {
let _: Span<CInt> = funcWithSafeWrapper3(v)
}

func callMixedFuncWithSafeWrapper1(_ s: Span<CInt>) {
let _: Span<CInt> = mixedFuncWithSafeWrapper1(s)
}

func callMixedFuncWithSafeWrapper2(_ v: borrowing VecOfInt) {
let _: Span<CInt> = mixedFuncWithSafeWrapper2(v, 73)
}

func callMixedFuncWithSafeWrapper3(_ s: Span<CInt>, _ p: UnsafeMutableBufferPointer<CInt>) {
unsafe mixedFuncWithSafeWrapper3(s, p)
}

func callMixedFuncWithSafeWrapper4(_ s: Span<CInt>, _ s2: Span<CInt>) {
mixedFuncWithSafeWrapper4(s, s2)
}

func callMixedFuncWithSafeWrapper5(_ s: ConstSpanOfInt, _ s2: Span<CInt>) {
unsafe mixedFuncWithSafeWrapper5(s, s2)
}

func callMixedFuncWithSafeWrapper6(_ s: ConstSpanOfInt, _ p: UnsafeMutableBufferPointer<CInt>) {
unsafe mixedFuncWithSafeWrapper6(s, p)
}

func callMixedFuncWithSafeWrapper7(_ p: UnsafeBufferPointer<CInt>) {
let _: ConstSpanOfInt = unsafe mixedFuncWithSafeWrapper7(p)
}