From ce27af61f7c5feb150b9f335c51d78160ea142dd Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 13:11:49 -0700 Subject: [PATCH 1/8] [Swiftify] make protocol extension methods public --- .../Sources/SwiftMacros/SwiftifyImportMacro.swift | 14 ++++++++++++-- .../Macros/SwiftifyImport/CountedBy/Protocol.swift | 14 +++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift index ea5af950daf3a..461807dbe4847 100644 --- a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift +++ b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift @@ -1808,11 +1808,21 @@ public struct SwiftifyImportProtocolMacro: ExtensionMacro { } let overloads = try arguments.map { let (method, args) = try parseProtocolMacroParam($0, methods: methods) - let function = try constructOverloadFunction( + let hasVisibilityModifier = method.modifiers.contains { modifier in + let modName = modifier.name.trimmed.text + return modName == "public" || modName == "internal" || modName == "open" + || modName == "private" || modName == "filePrivate" + } + let result = try constructOverloadFunction( forDecl: method, leadingTrivia: Trivia(), args: args, spanAvailability: spanAvailability, typeMappings: typeMappings) - return MemberBlockItemSyntax(decl: function) + guard let newMethod = result.as(FunctionDeclSyntax.self)? + .with(\.modifiers, method.modifiers + + (hasVisibilityModifier ? [] : [DeclModifierSyntax(name: .identifier("public"))])) else { + throw RuntimeError("expected FunctionDeclSyntax but got \(result.kind) for \(method.description)") + } + return MemberBlockItemSyntax(decl: newMethod) } return [ExtensionDeclSyntax(extensionKeyword: .identifier("extension"), extendedType: type, diff --git a/test/Macros/SwiftifyImport/CountedBy/Protocol.swift b/test/Macros/SwiftifyImport/CountedBy/Protocol.swift index 38729d224b9d9..3623caa797a71 100644 --- a/test/Macros/SwiftifyImport/CountedBy/Protocol.swift +++ b/test/Macros/SwiftifyImport/CountedBy/Protocol.swift @@ -42,7 +42,7 @@ protocol OverloadedProtocol { ------------------------------ extension SimpleProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func myFunc(_ ptr: UnsafeBufferPointer) { let len = CInt(exactly: ptr.count)! return unsafe myFunc(ptr.baseAddress!, len) @@ -53,7 +53,7 @@ extension SimpleProtocol { ------------------------------ extension SpanProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ ptr: Span) { let len = CInt(exactly: ptr.count)! return unsafe ptr.withUnsafeBufferPointer { _ptrPtr in @@ -61,7 +61,7 @@ extension SpanProtocol { } } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_lifetime(borrow self) @_disfavoredOverload +@_alwaysEmitIntoClient @_lifetime(borrow self) @_disfavoredOverload public func bar(_ len: CInt) -> Span { return unsafe _swiftifyOverrideLifetime(Span(_unsafeStart: unsafe bar(len), count: Int(len)), copying: ()) } @@ -71,7 +71,7 @@ extension SpanProtocol { ------------------------------ extension MixedProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public /// Some doc comment func foo(_ ptr: Span) { let len = CInt(exactly: ptr.count)! @@ -80,7 +80,7 @@ extension MixedProtocol { } } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func bar(_ ptr: UnsafeBufferPointer) { let len = CInt(exactly: ptr.count)! return unsafe bar(ptr.baseAddress!, len) @@ -91,13 +91,13 @@ extension MixedProtocol { ------------------------------ extension OverloadedProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ ptr: UnsafeBufferPointer) { let len1 = CInt(exactly: ptr.count)! return unsafe foo(ptr.baseAddress!, len1) } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(bar: UnsafeBufferPointer) { let len2 = CInt(exactly: bar.count)! return unsafe foo(bar: bar.baseAddress!, len2) From af819b94285dc0f979263891b7565639cae59521 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 22 Oct 2025 13:56:11 -0700 Subject: [PATCH 2/8] [ClangImporter] Attach _SwiftifyImportProtocol to imported protocols ...with bounds attributes This creates safe overloads for any methods in the protocol annotated with bounds information. Updates _SwiftifyImportProtocol to make the added overloads in the protocol public. rdar://144335990 --- lib/ClangImporter/ImportDecl.cpp | 2 + lib/ClangImporter/ImporterImpl.h | 1 + lib/ClangImporter/SwiftifyDecl.cpp | 133 +++++++++++++++--- lib/Sema/TypeCheckMacros.cpp | 7 +- .../swiftify-import/counted-by-protocol.swift | 133 ++++++++++++++++++ 5 files changed, 257 insertions(+), 19 deletions(-) create mode 100644 test/Interop/ObjC/swiftify-import/counted-by-protocol.swift diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 17dc9aa1a7194..8d6c763018baa 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -5881,6 +5881,8 @@ namespace { result->setMemberLoader(&Impl, 0); + Impl.swiftifyProtocol(result); + return result; } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index e17f4d8d20d6e..fef4067233d05 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -1827,6 +1827,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation void addOptionSetTypealiases(NominalTypeDecl *nominal); void swiftify(AbstractFunctionDecl *MappedDecl); + void swiftifyProtocol(NominalTypeDecl *MappedDecl); /// Find the lookup table that corresponds to the given Clang module. /// diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 7be08be0032a9..9b9293a2de502 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -16,6 +16,7 @@ #include "ImporterImpl.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/ASTPrinter.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsClangImporter.h" #include "swift/AST/ParameterList.h" @@ -151,6 +152,7 @@ class SwiftifyInfoPrinter { out << "\""; } +protected: void printSeparator() { if (!firstParam) { out << ", "; @@ -159,6 +161,7 @@ class SwiftifyInfoPrinter { } } +private: void printParamOrReturn(ssize_t pointerIndex) { if (pointerIndex == SELF_PARAM_INDEX) out << ".self"; @@ -296,12 +299,25 @@ static StringRef getAttributeName(const clang::CountAttributedType *CAT) { llvm_unreachable("CountAttributedType cannot be ended_by"); } } + +template +static bool getImplicitObjectParamAnnotation(const clang::ObjCMethodDecl* D) { + return false; // Only C++ methods have implicit params +} + +static size_t getNumParams(const clang::ObjCMethodDecl* D) { + return D->param_size(); +} +static size_t getNumParams(const clang::FunctionDecl* D) { + return D->getNumParams(); +} } // namespace +template static bool swiftifyImpl(ClangImporter::Implementation &Self, SwiftifyInfoPrinter &printer, const AbstractFunctionDecl *MappedDecl, - const clang::FunctionDecl *ClangDecl) { + const T *ClangDecl) { DLOG("Checking " << *ClangDecl << " for bounds and lifetime info\n"); // FIXME: for private macro generated functions we do not serialize the @@ -310,13 +326,6 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, ClangDecl->getAccess() == clang::AS_private) return false; - { - UnaliasedInstantiationVisitor visitor; - visitor.TraverseType(ClangDecl->getType()); - if (visitor.hasUnaliasedInstantiation) - return false; - } - clang::ASTContext &clangASTContext = Self.getClangASTContext(); // We only attach the macro if it will produce an overload. Any __counted_by @@ -332,9 +341,10 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, swiftReturnTy = ctorDecl->getResultInterfaceType(); else ABORT("Unexpected AbstractFunctionDecl subclass."); + clang::QualType clangReturnTy = ClangDecl->getReturnType(); bool returnIsStdSpan = printer.registerStdSpanTypeMapping( - swiftReturnTy, ClangDecl->getReturnType()); - auto *CAT = ClangDecl->getReturnType()->getAs(); + swiftReturnTy, clangReturnTy); + auto *CAT = clangReturnTy->getAs(); if (SwiftifiableCAT(clangASTContext, CAT, swiftReturnTy)) { printer.printCountedBy(CAT, SwiftifyInfoPrinter::RETURN_VALUE_INDEX); DLOG(" Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n"); @@ -361,13 +371,13 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, size_t swiftNumParams = MappedDecl->getParameters()->size() - (ClangDecl->isVariadic() ? 1 : 0); ASSERT((MappedDecl->isImportAsInstanceMember() == isClangInstanceMethod) == - (ClangDecl->getNumParams() == swiftNumParams)); + (getNumParams(ClangDecl) == swiftNumParams)); size_t selfParamIndex = MappedDecl->isImportAsInstanceMember() ? MappedDecl->getSelfIndex() - : ClangDecl->getNumParams(); + : getNumParams(ClangDecl); for (auto [index, clangParam] : llvm::enumerate(ClangDecl->parameters())) { - auto clangParamTy = clangParam->getType(); + clang::QualType clangParamTy = clangParam->getType(); int mappedIndex = index < selfParamIndex ? index : index > selfParamIndex ? index - 1 : SwiftifyInfoPrinter::SELF_PARAM_INDEX; @@ -383,13 +393,12 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, auto *CAT = clangParamTy->getAs(); if (CAT && mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX) { Self.diagnose(HeaderLoc(clangParam->getLocation()), - diag::warn_clang_ignored_bounds_on_self, - getAttributeName(CAT)); - auto swiftName = ClangDecl->getAttr(); + diag::warn_clang_ignored_bounds_on_self, getAttributeName(CAT)); + auto swiftName = ClangDecl->template getAttr(); ASSERT(swiftName && "free function mapped to instance method without swift_name??"); Self.diagnose(HeaderLoc(swiftName->getLocation()), - diag::note_swift_name_instance_method); + diag::note_swift_name_instance_method); } else if (SwiftifiableCAT(clangASTContext, CAT, swiftParamTy)) { printer.printCountedBy(CAT, mappedIndex); DLOG(" Found bounds info '" << clangParamTy << "' on parameter '" @@ -401,7 +410,7 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, paramHasBoundsInfo |= paramIsStdSpan; bool paramHasLifetimeInfo = false; - if (clangParam->hasAttr()) { + if (clangParam->template hasAttr()) { DLOG(" Found noescape attribute on parameter '" << *clangParam << "'\n"); printer.printNonEscaping(mappedIndex); paramHasLifetimeInfo = true; @@ -435,6 +444,47 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, return attachMacro; } +class SwiftifyProtocolInfoPrinter : public SwiftifyInfoPrinter { +private: + ClangImporter::Implementation &ImporterImpl; + +public: + SwiftifyProtocolInfoPrinter(ClangImporter::Implementation &ImporterImpl, + clang::ASTContext &ctx, ASTContext &SwiftContext, + llvm::raw_ostream &out, + MacroDecl &SwiftifyImportDecl) + : SwiftifyInfoPrinter(ctx, SwiftContext, out, SwiftifyImportDecl), + ImporterImpl(ImporterImpl) {} + + bool printMethod(const FuncDecl *Method) { + // don't emit .method() if we know it's going to be empty + auto ClangDecl = dyn_cast_or_null(Method->getClangDecl()); + if (!ClangDecl) + return false; + + printSeparator(); + out << ".method(signature: \""; + printMethodSignature(Method); + out << "\", paramInfo: ["; + // reset firstParam inside paramInfo array. At this point firstParam will + // always be false, so no need to save the current value. + assert(!firstParam); + firstParam = true; + bool hadAttributes = swiftifyImpl(ImporterImpl, *this, Method, ClangDecl); + firstParam = false; + out << "])"; + return hadAttributes; + } + +private: + void printMethodSignature(const FuncDecl *Method) { + auto options = + PrintOptions::printForDiagnostics(AccessLevel::Private, true); + StreamPrinter printer(out); + Method->print(printer, options); + } +}; + void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { if (!SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers) || SwiftContext.ClangImporterOpts.DisableSafeInteropWrappers) @@ -443,6 +493,13 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { if (!ClangDecl) return; + { + UnaliasedInstantiationVisitor visitor; + visitor.TraverseType(ClangDecl->getType()); + if (visitor.hasUnaliasedInstantiation) + return; + } + MacroDecl *SwiftifyImportDecl = dyn_cast_or_null(getKnownSingleDecl(SwiftContext, "_SwiftifyImport")); if (!SwiftifyImportDecl) return; @@ -479,3 +536,43 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { } } +void ClangImporter::Implementation::swiftifyProtocol( + NominalTypeDecl *MappedDecl) { + if (!SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers) || + SwiftContext.ClangImporterOpts.DisableSafeInteropWrappers) + return; + if (!isa(MappedDecl)) + return; + + MacroDecl *SwiftifyImportDecl = dyn_cast_or_null( + getKnownSingleDecl(SwiftContext, "_SwiftifyImportProtocol")); + if (!SwiftifyImportDecl) + return; + + DLOG("Checking " << MappedDecl->getName() << " protocol for methods with bounds and lifetime info\n"); + llvm::SmallString<128> MacroString; + { + llvm::raw_svector_ostream out(MacroString); + out << "@_SwiftifyImportProtocol"; + + bool hasBoundsAttributes = false; + SwiftifyProtocolInfoPrinter printer(*this, getClangASTContext(), + SwiftContext, out, *SwiftifyImportDecl); + for (Decl *SwiftMember : + cast(MappedDecl)->getAllMembers()) { + FuncDecl *SwiftDecl = dyn_cast(SwiftMember); + if (!SwiftDecl) + continue; + hasBoundsAttributes |= printer.printMethod(SwiftDecl); + } + + if (!hasBoundsAttributes) + return; + printer.printAvailability(); + printer.printTypeMapping(); + } + + DLOG("Attaching safe interop macro: " << MacroString << "\n"); + importNontrivialAttribute(MappedDecl, MacroString); +} + diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 9e57b5a012a13..6df797d829003 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1360,6 +1360,7 @@ swift::expandFreestandingMacro(MacroExpansionDecl *med) { return macroSourceFile->getBufferID(); } + static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, CustomAttr *attr, bool passParentContext, MacroRole role, @@ -1370,7 +1371,11 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, dc = attachedTo->getDeclContext(); } else if (role == MacroRole::Conformance || role == MacroRole::Extension) { // Conformance macros always expand to extensions at file-scope. - dc = attachedTo->getDeclContext()->getParentSourceFile(); + dc = attachedTo->getDeclContext(); + if (!isa(dc->getModuleScopeContext())) + dc = dc->getParentSourceFile(); + else // decls imported from clang do not have a SourceFile + ASSERT(isa(dc) && !isa(dc)); } else { dc = attachedTo->getInnermostDeclContext(); } diff --git a/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift new file mode 100644 index 0000000000000..efe843e4d6037 --- /dev/null +++ b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift @@ -0,0 +1,133 @@ +// REQUIRES: swift_feature_SafeInteropWrappers + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -verify -Xcc -Wno-nullability-completeness +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -dump-macro-expansions 2> %t/expansions.out +// RUN: %diff %t/expansions.out %t/expansions.expected + +//--- Inputs/module.modulemap +module CountedByProtocolClang { + header "counted-by-protocol.h" + export * +} + +//--- Inputs/counted-by-protocol.h +#pragma once + +#define __counted_by(x) __attribute__((__counted_by__(x))) + +int foo(int len, int * __counted_by(len) p); +@protocol CountedByProtocol + - (void) simple:(int)len :(int * __counted_by(len))p; + - (void) shared:(int)len :(int * __counted_by(len))p1 :(int * __counted_by(len))p2; + - (void) complexExpr:(int)len :(int) offset :(int * __counted_by(len - offset))p; + - (void) nullUnspecified:(int)len :(int * __counted_by(len) _Null_unspecified)p; + - (void) nonnull:(int)len :(int * __counted_by(len) _Nonnull)p; + - (void) nullable:(int)len :(int * __counted_by(len) _Nullable)p; + - (int * __counted_by(len)) returnPointer:(int)len; + + + (void) staticMethod:(int)len :(int * __counted_by(len))p; +@end + +__attribute__((swift_attr("@_SwiftifyImportProtocol(.method(signature: \"func swiftAttr(_ len: Int32, _ p: UnsafeMutablePointer!)\", paramInfo: [.countedBy(pointer: .param(2), count: \"len\")]))"))) +@protocol SwiftAttrProtocol + - (void)swiftAttr:(int)len :(int *)p; +@end + +//--- expansions.expected +@__swiftmacro_So17CountedByProtocol015_SwiftifyImportC0fMe_.swift +------------------------------ +extension CountedByProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func shared(_ p1: UnsafeMutableBufferPointer, _ p2: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p1.count)! + if p2.count != len { + fatalError("bounds check failure in shared: expected \(len) but got \(p2.count)") + } + return unsafe shared(len, p1.baseAddress!, p2.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func complexExpr(_ len: Int32, _ offset: Int32, _ p: UnsafeMutableBufferPointer) { + let _pCount = p.count + if _pCount != len - offset { + fatalError("bounds check failure in complexExpr: expected \(len - offset) but got \(_pCount)") + } + return unsafe complexExpr(len, offset, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nullUnspecified(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe nullUnspecified(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nonnull(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe nonnull(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nullable(_ p: UnsafeMutableBufferPointer?) { + let len = Int32(exactly: unsafe p?.count ?? 0)! + return unsafe nullable(len, p?.baseAddress) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func returnPointer(_ len: Int32) -> UnsafeMutableBufferPointer { + return unsafe UnsafeMutableBufferPointer(start: unsafe returnPointer(len), count: Int(len)) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload + static public func staticMethod(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe staticMethod(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So17SwiftAttrProtocol015_SwiftifyImportC0fMe_.swift +------------------------------ +extension SwiftAttrProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func swiftAttr(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe swiftAttr(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So3foo15_SwiftifyImportfMp_.swift +------------------------------ +/// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ p: UnsafeMutableBufferPointer) -> Int32 { + let len = Int32(exactly: p.count)! + return unsafe foo(len, p.baseAddress!) +} +------------------------------ +//--- counted-by-protocol.swift +import CountedByProtocolClang + +@inlinable +public func call(p: UnsafeMutableBufferPointer, x: CInt, y: CInt, a: CountedByProtocol, b: SwiftAttrProtocol) { + a.simple(p) + a.shared(p, p) + a.complexExpr(x, y, p) + a.nullUnspecified(p) + a.nonnull(p) + a.nullable(p) + let _: UnsafeMutableBufferPointer = a.returnPointer(x) + let r2 = a.returnPointer(x) + let _: UnsafeMutablePointer? = r2 // make sure the original is the favored overload + b.swiftAttr(p) + let _ = foo(p) +} From 72439d35a7b50d8b9c048804c6779ced7293d604 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 13:14:21 -0700 Subject: [PATCH 3/8] assert -> ASSERT --- lib/ClangImporter/SwiftifyDecl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 9b9293a2de502..67976d27c085b 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -37,7 +37,7 @@ namespace { ValueDecl *getKnownSingleDecl(ASTContext &SwiftContext, StringRef DeclName) { SmallVector decls; SwiftContext.lookupInSwiftModule(DeclName, decls); - assert(decls.size() < 2); + ASSERT(decls.size() < 2); if (decls.size() != 1) return nullptr; return decls[0]; } @@ -468,7 +468,7 @@ class SwiftifyProtocolInfoPrinter : public SwiftifyInfoPrinter { out << "\", paramInfo: ["; // reset firstParam inside paramInfo array. At this point firstParam will // always be false, so no need to save the current value. - assert(!firstParam); + ASSERT(!firstParam); firstParam = true; bool hadAttributes = swiftifyImpl(ImporterImpl, *this, Method, ClangDecl); firstParam = false; From 48ad5286570be748c2f1f7e337d383507ff42e00 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 16:14:47 -0700 Subject: [PATCH 4/8] silence backtrace warning --- test/Interop/ObjC/swiftify-import/counted-by-protocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift index efe843e4d6037..a15dee63f3063 100644 --- a/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift +++ b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift @@ -4,7 +4,7 @@ // RUN: split-file %s %t // RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -verify -Xcc -Wno-nullability-completeness -// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -dump-macro-expansions 2> %t/expansions.out +// RUN: env SWIFT_BACKTRACE="" %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -dump-macro-expansions 2> %t/expansions.out // RUN: %diff %t/expansions.out %t/expansions.expected //--- Inputs/module.modulemap From 14c79d292c6c14ae626f23ee35bad136c50bb523 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 18:41:57 -0700 Subject: [PATCH 5/8] [ClangImporter] don't emit empty `.method()` This updates SwiftifyImportProtocolPrinter such that it no longer emits anything for methods without bounds or lifetime info. Previously we would not attach the macro if no methods in the protocol contained bounds or lifetime info. However if one of the methods did, we would still emit `.method(signature: "func foo()", paramInfo: [])` for the methods without bounds of lifetime info. This would result in overloads being generated, like so: ``` @_alwaysEmitIntoClient @_disfavoredOverload public func foo() { return unsafe foo() } ``` As part of this change, SwiftifyImportPrinter is now an abstract parent type for SwiftifyImportProtocolPrinter, and the new SwiftifyImportFunctionPrinter. Instead of SwiftifyImportProtocolPrinter inheriting the function printing, `printMethod` instead creates a new SwiftifyImportFunctionPrinter each time, with a new output string. If it output anything interesting the result is forwarded, otherwise it is discarded. --- lib/ClangImporter/SwiftifyDecl.cpp | 172 ++++++++++-------- .../ObjC/swiftify-import/negative.swift | 97 ++++++++++ 2 files changed, 190 insertions(+), 79 deletions(-) create mode 100644 test/Interop/ObjC/swiftify-import/negative.swift diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 67976d27c085b..f0e0a5ed81588 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -42,74 +42,25 @@ ValueDecl *getKnownSingleDecl(ASTContext &SwiftContext, StringRef DeclName) { return decls[0]; } -class SwiftifyInfoPrinter { -public: +struct SwiftifyInfoPrinter { static const ssize_t SELF_PARAM_INDEX = -2; static const ssize_t RETURN_VALUE_INDEX = -1; clang::ASTContext &ctx; ASTContext &SwiftContext; - llvm::raw_ostream &out; + llvm::raw_svector_ostream &out; MacroDecl &SwiftifyImportDecl; bool firstParam = true; - llvm::StringMap typeMapping; + llvm::StringMap &typeMapping; +protected: SwiftifyInfoPrinter(clang::ASTContext &ctx, ASTContext &SwiftContext, - llvm::raw_ostream &out, MacroDecl &SwiftifyImportDecl) + llvm::raw_svector_ostream &out, + MacroDecl &SwiftifyImportDecl, + llvm::StringMap &typeMapping) : ctx(ctx), SwiftContext(SwiftContext), out(out), - SwiftifyImportDecl(SwiftifyImportDecl) { - out << "("; - } - ~SwiftifyInfoPrinter() { out << ")"; } - - void printCountedBy(const clang::CountAttributedType *CAT, - ssize_t pointerIndex) { - printSeparator(); - clang::Expr *countExpr = CAT->getCountExpr(); - bool isSizedBy = CAT->isCountInBytes(); - out << "."; - if (isSizedBy) - out << "sizedBy"; - else - out << "countedBy"; - out << "(pointer: "; - printParamOrReturn(pointerIndex); - out << ", "; - if (isSizedBy) - out << "size"; - else - out << "count"; - out << ": \""; - countExpr->printPretty( - out, {}, {ctx.getLangOpts()}); // TODO: map clang::Expr to Swift Expr - out << "\")"; - } - - void printNonEscaping(int idx) { - printSeparator(); - out << ".nonescaping(pointer: "; - printParamOrReturn(idx); - out << ")"; - } - - void printLifetimeboundReturn(int idx, bool borrow) { - printSeparator(); - out << ".lifetimeDependence(dependsOn: "; - printParamOrReturn(idx); - out << ", pointer: .return, type: "; - out << (borrow ? ".borrow" : ".copy"); - out << ")"; - } - - bool registerStdSpanTypeMapping(Type swiftType, const clang::QualType clangType) { - const auto *decl = clangType->getAsTagDecl(); - if (decl && decl->isInStdNamespace() && decl->getName() == "span") { - typeMapping.insert(std::make_pair( - swiftType->getString(), swiftType->getDesugaredType()->getString())); - return true; - } - return false; - } + SwiftifyImportDecl(SwiftifyImportDecl), typeMapping(typeMapping) {} +public: void printTypeMapping() { printSeparator(); out << "typeMappings: ["; @@ -130,7 +81,6 @@ class SwiftifyInfoPrinter { out << "spanAvailability: "; printAvailabilityOfType("Span"); } - private: bool hasMacroParameter(StringRef ParamName) { for (auto *Param : *SwiftifyImportDecl.parameterList) @@ -160,6 +110,63 @@ class SwiftifyInfoPrinter { firstParam = false; } } +}; + +struct SwiftifyInfoFunctionPrinter : public SwiftifyInfoPrinter { + SwiftifyInfoFunctionPrinter(clang::ASTContext &ctx, ASTContext &SwiftContext, + llvm::raw_svector_ostream &out, + MacroDecl &SwiftifyImportDecl, + llvm::StringMap &typeMapping) + : SwiftifyInfoPrinter(ctx, SwiftContext, out, SwiftifyImportDecl, typeMapping) {} + + void printCountedBy(const clang::CountAttributedType *CAT, + ssize_t pointerIndex) { + printSeparator(); + clang::Expr *countExpr = CAT->getCountExpr(); + bool isSizedBy = CAT->isCountInBytes(); + out << "."; + if (isSizedBy) + out << "sizedBy"; + else + out << "countedBy"; + out << "(pointer: "; + printParamOrReturn(pointerIndex); + out << ", "; + if (isSizedBy) + out << "size"; + else + out << "count"; + out << ": \""; + countExpr->printPretty( + out, {}, {ctx.getLangOpts()}); // TODO: map clang::Expr to Swift Expr + out << "\")"; + } + + void printNonEscaping(int idx) { + printSeparator(); + out << ".nonescaping(pointer: "; + printParamOrReturn(idx); + out << ")"; + } + + void printLifetimeboundReturn(int idx, bool borrow) { + printSeparator(); + out << ".lifetimeDependence(dependsOn: "; + printParamOrReturn(idx); + out << ", pointer: .return, type: "; + out << (borrow ? ".borrow" : ".copy"); + out << ")"; + } + + bool registerStdSpanTypeMapping(Type swiftType, const clang::QualType clangType) { + const auto *decl = clangType->getAsTagDecl(); + if (decl && decl->isInStdNamespace() && decl->getName() == "span") { + typeMapping.insert(std::make_pair( + swiftType->getString(), swiftType->getDesugaredType()->getString())); + return true; + } + return false; + } private: void printParamOrReturn(ssize_t pointerIndex) { @@ -315,7 +322,7 @@ static size_t getNumParams(const clang::FunctionDecl* D) { template static bool swiftifyImpl(ClangImporter::Implementation &Self, - SwiftifyInfoPrinter &printer, + SwiftifyInfoFunctionPrinter &printer, const AbstractFunctionDecl *MappedDecl, const T *ClangDecl) { DLOG("Checking " << *ClangDecl << " for bounds and lifetime info\n"); @@ -451,28 +458,29 @@ class SwiftifyProtocolInfoPrinter : public SwiftifyInfoPrinter { public: SwiftifyProtocolInfoPrinter(ClangImporter::Implementation &ImporterImpl, clang::ASTContext &ctx, ASTContext &SwiftContext, - llvm::raw_ostream &out, - MacroDecl &SwiftifyImportDecl) - : SwiftifyInfoPrinter(ctx, SwiftContext, out, SwiftifyImportDecl), + llvm::raw_svector_ostream &out, + MacroDecl &SwiftifyImportDecl, + llvm::StringMap &typeMapping) + : SwiftifyInfoPrinter(ctx, SwiftContext, out, SwiftifyImportDecl, typeMapping), ImporterImpl(ImporterImpl) {} bool printMethod(const FuncDecl *Method) { - // don't emit .method() if we know it's going to be empty auto ClangDecl = dyn_cast_or_null(Method->getClangDecl()); if (!ClangDecl) return false; - printSeparator(); - out << ".method(signature: \""; - printMethodSignature(Method); - out << "\", paramInfo: ["; - // reset firstParam inside paramInfo array. At this point firstParam will - // always be false, so no need to save the current value. - ASSERT(!firstParam); - firstParam = true; - bool hadAttributes = swiftifyImpl(ImporterImpl, *this, Method, ClangDecl); - firstParam = false; - out << "])"; + llvm::SmallString<128> paramInfoString; + llvm::raw_svector_ostream tmpOut(paramInfoString); + + SwiftifyInfoFunctionPrinter methodPrinter(ctx, SwiftContext, tmpOut, + SwiftifyImportDecl, typeMapping); + bool hadAttributes = swiftifyImpl(ImporterImpl, methodPrinter, Method, ClangDecl); + if (hadAttributes) { + printSeparator(); + out << ".method(signature: \""; + printMethodSignature(Method); + out << "\", paramInfo: [" << paramInfoString << "])"; + } return hadAttributes; } @@ -507,13 +515,16 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { llvm::SmallString<128> MacroString; { llvm::raw_svector_ostream out(MacroString); - out << "@_SwiftifyImport"; + out << "@_SwiftifyImport("; - SwiftifyInfoPrinter printer(getClangASTContext(), SwiftContext, out, *SwiftifyImportDecl); + llvm::StringMap typeMapping; + SwiftifyInfoFunctionPrinter printer(getClangASTContext(), SwiftContext, out, + *SwiftifyImportDecl, typeMapping); if (!swiftifyImpl(*this, printer, MappedDecl, ClangDecl)) return; printer.printAvailability(); printer.printTypeMapping(); + out << ")"; } DLOG("Attaching safe interop macro: " << MacroString << "\n"); @@ -553,11 +564,13 @@ void ClangImporter::Implementation::swiftifyProtocol( llvm::SmallString<128> MacroString; { llvm::raw_svector_ostream out(MacroString); - out << "@_SwiftifyImportProtocol"; + out << "@_SwiftifyImportProtocol("; bool hasBoundsAttributes = false; + llvm::StringMap typeMapping; SwiftifyProtocolInfoPrinter printer(*this, getClangASTContext(), - SwiftContext, out, *SwiftifyImportDecl); + SwiftContext, out, *SwiftifyImportDecl, + typeMapping); for (Decl *SwiftMember : cast(MappedDecl)->getAllMembers()) { FuncDecl *SwiftDecl = dyn_cast(SwiftMember); @@ -570,6 +583,7 @@ void ClangImporter::Implementation::swiftifyProtocol( return; printer.printAvailability(); printer.printTypeMapping(); + out << ")"; } DLOG("Attaching safe interop macro: " << MacroString << "\n"); diff --git a/test/Interop/ObjC/swiftify-import/negative.swift b/test/Interop/ObjC/swiftify-import/negative.swift new file mode 100644 index 0000000000000..2ab4396a6ac5a --- /dev/null +++ b/test/Interop/ObjC/swiftify-import/negative.swift @@ -0,0 +1,97 @@ +// REQUIRES: swift_feature_SafeInteropWrappers + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %t -enable-experimental-feature SafeInteropWrappers %t/test.swift -verify -Xcc -Wno-nullability-completeness +// RUN: env SWIFT_BACKTRACE="" %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %t -enable-experimental-feature SafeInteropWrappers %t/test.swift -dump-macro-expansions 2> %t/expansions.out +// RUN: %diff %t/expansions.out %t/expansions.expected + +//--- test.h +#pragma once + +#define __counted_by(x) __attribute__((__counted_by__(x))) + +@protocol NoBounds + - (void) ignore; +@end + +@protocol NoYesNo + - (void) ignore; + - (void) simple:(int)len :(int * __counted_by(len))p; + - (void) ignore2; +@end + +@protocol YesNo + - (void) simple:(int)len :(int * __counted_by(len))p; + - (void) ignore; +@end + +@protocol YesNoYes + - (void) simple:(int)len :(int * __counted_by(len))p; + - (void) ignore; + - (void) simple2:(int)len :(int * __counted_by(len))p; +@end + +//--- expansions.expected +@__swiftmacro_So05NoYesA023_SwiftifyImportProtocolfMe_.swift +------------------------------ +extension NoYesNo { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So5YesNo23_SwiftifyImportProtocolfMe_.swift +------------------------------ +extension YesNo { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So05YesNoA023_SwiftifyImportProtocolfMe_.swift +------------------------------ +extension YesNoYes { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple2(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple2(len, p.baseAddress!) + } +} +------------------------------ +//--- test.swift +import TestClang + +@inlinable +public func call(p: UnsafeMutableBufferPointer, x: NoBounds, y: NoYesNo, z: YesNo, å: YesNoYes) { + x.ignore() + y.ignore() + y.simple(p) + z.ignore() + z.simple(p) + å.ignore() + å.simple(p) + å.simple2(p) +} + +//--- module.modulemap +module TestClang { + header "test.h" + export * +} + + From 1c0c55863ad941e2d4da4942275352d703de316d Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 19:39:06 -0700 Subject: [PATCH 6/8] add test case for [Mutable]Span wrappers in ObjC protocols --- .../counted-by-protocol-noescape.swift | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 test/Interop/ObjC/swiftify-import/counted-by-protocol-noescape.swift diff --git a/test/Interop/ObjC/swiftify-import/counted-by-protocol-noescape.swift b/test/Interop/ObjC/swiftify-import/counted-by-protocol-noescape.swift new file mode 100644 index 0000000000000..480548ddab18e --- /dev/null +++ b/test/Interop/ObjC/swiftify-import/counted-by-protocol-noescape.swift @@ -0,0 +1,178 @@ +// REQUIRES: swift_feature_SafeInteropWrappers +// REQUIRES: swift_feature_LifetimeDependence + +// REQUIRES: foundation + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %t -enable-experimental-feature SafeInteropWrappers -enable-experimental-feature LifetimeDependence %t/test.swift -verify -Xcc -Wno-nullability-completeness +// RUN: env SWIFT_BACKTRACE="" %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %t -enable-experimental-feature SafeInteropWrappers -enable-experimental-feature LifetimeDependence %t/test.swift -dump-macro-expansions 2> %t/expansions.out +// RUN: %diff %t/expansions.out %t/expansions.expected + +//--- test.h +#pragma once + +#include + +// __counted_by definition inherited from Foundation.h +#define __noescape __attribute__((noescape)) +#define __lifetimebound __attribute__((lifetimebound)) + +void foo(int len, int * __counted_by(len) p __noescape); +@protocol TestProtocol + - (void) bar:(int * __noescape)p _Nullable:(int)asdf; + - (void) simple:(int)len :(int * __counted_by(len) __noescape)p; + - (void) shared:(int)len :(int * __counted_by(len) __noescape)p1 :(int * __counted_by(len) __noescape)p2; + - (void) complexExpr:(int)len :(int) offset :(int * __counted_by(len - offset) __noescape)p; + - (void) nullUnspecified:(int)len :(int * __counted_by(len) _Null_unspecified __noescape)p; + - (void) nonnull:(int)len :(int * __counted_by(len) _Nonnull __noescape)p; + - (void) nullable:(int)len :(int * __counted_by(len) _Nullable __noescape)p; + - (int * __counted_by(len)) returnPointer:(int)len : (int * __counted_by(len) _Nullable) __lifetimebound p; + - (void) mixedEscapability:(int)len :(int * __counted_by(len) __noescape)p1 :(int * __counted_by(len))p2; +@end + +@protocol StaticProtocol + + (void) staticMethod:(int)len :(int * __counted_by(len) __noescape)p; +@end + +@interface StaticInterface : NSObject {} +@end + +//--- expansions.expected +@__swiftmacro_So12TestProtocol015_SwiftifyImportB0fMe_.swift +------------------------------ +extension TestProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public + func simple(_ p: inout MutableSpan) { + let len = Int32(exactly: p.count)! + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe simple(len, _pPtr.baseAddress!) + } + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p1: copy p1) @_lifetime(p2: copy p2) @_disfavoredOverload public + func shared(_ p1: inout MutableSpan, _ p2: inout MutableSpan) { + let len = Int32(exactly: p1.count)! + if p2.count != len { + fatalError("bounds check failure in shared: expected \(len) but got \(p2.count)") + } + return unsafe p2.withUnsafeMutableBufferPointer { _p2Ptr in + return unsafe p1.withUnsafeMutableBufferPointer { _p1Ptr in + return unsafe shared(len, _p1Ptr.baseAddress!, _p2Ptr.baseAddress!) + } + } + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public + func complexExpr(_ len: Int32, _ offset: Int32, _ p: inout MutableSpan) { + let _pCount = p.count + if _pCount != len - offset { + fatalError("bounds check failure in complexExpr: expected \(len - offset) but got \(_pCount)") + } + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe complexExpr(len, offset, _pPtr.baseAddress!) + } + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public + func nullUnspecified(_ p: inout MutableSpan) { + let len = Int32(exactly: p.count)! + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe nullUnspecified(len, _pPtr.baseAddress!) + } + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public + func nonnull(_ p: inout MutableSpan) { + let len = Int32(exactly: p.count)! + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe nonnull(len, _pPtr.baseAddress!) + } + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public + func nullable(_ p: inout MutableSpan?) { + let len = Int32(exactly: p?.count ?? 0)! + return { () in + return if p == nil { + unsafe nullable(len, nil) + } else { + unsafe p!.withUnsafeMutableBufferPointer { _pPtr in + return unsafe nullable(len, _pPtr.baseAddress) + } + } + }() + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(copy p) @_lifetime(p: copy p) @_disfavoredOverload public + func returnPointer(_ p: inout MutableSpan?) -> MutableSpan { + let len = Int32(exactly: p?.count ?? 0)! + return unsafe _swiftifyOverrideLifetime(MutableSpan(_unsafeStart: { () in + return if p == nil { + unsafe returnPointer(len, nil) + } else { + unsafe p!.withUnsafeMutableBufferPointer { _pPtr in + return unsafe returnPointer(len, _pPtr.baseAddress) + } + } + }(), count: Int(len)), copying: ()) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p1: copy p1) @_disfavoredOverload public + func mixedEscapability(_ p1: inout MutableSpan, _ p2: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p1.count)! + if p2.count != len { + fatalError("bounds check failure in mixedEscapability: expected \(len) but got \(p2.count)") + } + return unsafe p1.withUnsafeMutableBufferPointer { _p1Ptr in + return unsafe mixedEscapability(len, _p1Ptr.baseAddress!, p2.baseAddress!) + } + } +} +------------------------------ +@__swiftmacro_So14StaticProtocol015_SwiftifyImportB0fMe_.swift +------------------------------ +extension StaticProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload + static public func staticMethod(_ p: inout MutableSpan) { + let len = Int32(exactly: p.count)! + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe staticMethod(len, _pPtr.baseAddress!) + } + } +} +------------------------------ +@__swiftmacro_So3foo15_SwiftifyImportfMp_.swift +------------------------------ +/// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *) @_lifetime(p: copy p) @_disfavoredOverload public func foo(_ p: inout MutableSpan) { + let len = Int32(exactly: p.count)! + return unsafe p.withUnsafeMutableBufferPointer { _pPtr in + return unsafe foo(len, _pPtr.baseAddress!) + } +} +------------------------------ +//--- test.swift +import TestClang + +@inlinable +public func call(p: inout MutableSpan, p2: inout MutableSpan, p3: inout MutableSpan?, a: TestProtocol) { + a.simple(&p) + a.shared(&p, &p2) + a.complexExpr(2, 3, &p) + a.nullUnspecified(&p) + a.nonnull(&p) + a.nullable(&p3) + let _: MutableSpan = a.returnPointer(&p3) + StaticInterface.staticMethod(&p2) + foo(&p) +} + +//--- module.modulemap +module TestClang { + header "test.h" + export * +} From 7025b034495f1ba211e07d225820591d1662b9c6 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Mon, 10 Nov 2025 19:18:17 -0800 Subject: [PATCH 7/8] Update lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift Co-authored-by: John Hui --- lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift index 461807dbe4847..411fb72efee67 100644 --- a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift +++ b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift @@ -1808,10 +1808,11 @@ public struct SwiftifyImportProtocolMacro: ExtensionMacro { } let overloads = try arguments.map { let (method, args) = try parseProtocolMacroParam($0, methods: methods) - let hasVisibilityModifier = method.modifiers.contains { modifier in - let modName = modifier.name.trimmed.text - return modName == "public" || modName == "internal" || modName == "open" - || modName == "private" || modName == "filePrivate" + let hasVisibilityModifier = method.modifiers.contains { + return switch $0.name.trimmed.text { + case "open", "public", "package", "internal", "fileprivate", "private": true + default: false + } } let result = try constructOverloadFunction( forDecl: method, leadingTrivia: Trivia(), args: args, From 959d6a656b93634e6033b47f215d6019fe0e6b32 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Mon, 10 Nov 2025 19:18:35 -0800 Subject: [PATCH 8/8] Update lib/Sema/TypeCheckMacros.cpp Co-authored-by: John Hui --- lib/Sema/TypeCheckMacros.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 6df797d829003..fea4e54872155 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1374,8 +1374,8 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, dc = attachedTo->getDeclContext(); if (!isa(dc->getModuleScopeContext())) dc = dc->getParentSourceFile(); - else // decls imported from clang do not have a SourceFile - ASSERT(isa(dc) && !isa(dc)); + else + ASSERT(isa(dc) && !isa(dc) && "decls imported from Clang should not have a SourceFile"); } else { dc = attachedTo->getInnermostDeclContext(); }