From 2aeaf2cd89c95adbcea550f48ee0fc048152a4fb Mon Sep 17 00:00:00 2001 From: Sam Pyankov Date: Mon, 23 Feb 2026 15:41:02 -0800 Subject: [PATCH] ASTPrinter: Filter inherited constructors from restricted modules Skip printing inherited constructors defined in modules that are not publicly imported. This prevents interface verification errors where types are used but their modules aren't in the import list. rdar://168101765 --- lib/AST/ASTPrinter.cpp | 52 +++++++ .../restricted-import-inherited-init.swift | 139 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 test/ModuleInterface/restricted-import-inherited-init.swift diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 50e6a465866..93934c53659 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2447,6 +2447,58 @@ bool isNonSendableExtension(const Decl *D) { bool ShouldPrintChecker::shouldPrint(const Decl *D, const PrintOptions &Options) { + // Skip constructors defined in Objective-C categories that are not + // publicly imported according to the interface type being generated + if (auto *CD = dyn_cast(D)) { + if (CD->isImplicit() && Options.IsForSwiftInterface && + Options.CurrentModule) { + // For inherited constructors, check the original declaration + auto *checkDecl = CD; + if (auto *overridden = CD->getOverriddenDecl()) { + checkDecl = overridden; + } + auto definingModule = checkDecl->getModuleContext(); + // Check how the defining module is imported + if (auto *SF = CD->getDeclContext()->getParentSourceFile()) { + // Don't check import status for self-imports + if (definingModule != SF->getParentModule()) { + auto restrictedKind = SF->getRestrictedImportKind(definingModule); + switch (restrictedKind) { + // MissingImport and ImplementationOnly: skip in all interfaces + case RestrictedImportKind::MissingImport: + case RestrictedImportKind::ImplementationOnly: + return false; + // SPIOnly: skip in public interfaces + case RestrictedImportKind::SPIOnly: + if (Options.printPublicInterface()) + return false; + break; + case RestrictedImportKind::None: + break; + } + // Check access level + if (auto importAccess = SF->getImportAccessLevel(definingModule)) { + auto accessLevel = importAccess->accessLevel; + switch (accessLevel) { + // Internal and below: skip in all interfaces + case AccessLevel::Private: + case AccessLevel::FilePrivate: + case AccessLevel::Internal: + return false; + // Package: skip in non-package interfaces only + case AccessLevel::Package: + if (!Options.printPackageInterface()) + return false; + break; + default: + break; + } + } + } + } + } + } + if (auto *ED = dyn_cast(D)) { if (Options.printExtensionContentAsMembers(ED)) return false; diff --git a/test/ModuleInterface/restricted-import-inherited-init.swift b/test/ModuleInterface/restricted-import-inherited-init.swift new file mode 100644 index 00000000000..61a3c6c638e --- /dev/null +++ b/test/ModuleInterface/restricted-import-inherited-init.swift @@ -0,0 +1,139 @@ +// REQUIRES: OS=macosx +// RUN: %empty-directory(%t) +// RUN: mkdir -p %t/Inputs/implementation-only-init +// RUN: split-file %s %t + +// Test with 'internal import' syntax +// RUN: %target-swift-frontend -emit-module %t/test.swift -module-name Client -enable-library-evolution -swift-version 6 -I %t/Inputs/implementation-only-init -emit-module-interface-path %t/Client.swiftinterface -emit-private-module-interface-path %t/Client.private.swiftinterface + +// RUN: %FileCheck %s -check-prefix=CHECK < %t/Client.private.swiftinterface +// RUN: %FileCheck %s -check-prefix=CHECK-PUBLIC < %t/Client.swiftinterface + +// Test with '@_implementationOnly import' syntax +// RUN: %target-swift-frontend -emit-module %t/test2.swift -module-name Client2 -enable-library-evolution -swift-version 6 -I %t/Inputs/implementation-only-init -emit-module-interface-path %t/Client2.swiftinterface -emit-private-module-interface-path %t/Client2.private.swiftinterface + +// RUN: %FileCheck %s -check-prefix=CHECK < %t/Client2.private.swiftinterface + +// Test with '@_spiOnly import' syntax +// RUN: %target-swift-frontend -emit-module %t/test3.swift -module-name Client3 -enable-library-evolution -swift-version 6 -I %t/Inputs/implementation-only-init -emit-module-interface-path %t/Client3.swiftinterface -emit-private-module-interface-path %t/Client3.private.swiftinterface -experimental-spi-only-imports + +// RUN: %FileCheck %s -check-prefix=CHECK-SPI-PRIVATE < %t/Client3.private.swiftinterface +// RUN: %FileCheck %s -check-prefix=CHECK-SPI-PUBLIC < %t/Client3.swiftinterface + +// Test with 'package import' syntax +// RUN: %target-swift-frontend -emit-module %t/test4.swift -module-name Client4 -enable-library-evolution -swift-version 6 -I %t/Inputs/implementation-only-init -emit-module-interface-path %t/Client4.swiftinterface -emit-private-module-interface-path %t/Client4.private.swiftinterface -emit-package-module-interface-path %t/Client4.package.swiftinterface -package-name MyPackage + +// RUN: %FileCheck %s -check-prefix=CHECK-PRIVATE < %t/Client4.private.swiftinterface +// RUN: %FileCheck %s -check-prefix=CHECK-PACKAGE < %t/Client4.package.swiftinterface + +// Test with 'private import' syntax +// RUN: %target-swift-frontend -emit-module %t/test5.swift -module-name Client5 -enable-library-evolution -swift-version 6 -I %t/Inputs/implementation-only-init -emit-module-interface-path %t/Client5.swiftinterface -emit-private-module-interface-path %t/Client5.private.swiftinterface + +// RUN: %FileCheck %s -check-prefix=CHECK-PRIVATE < %t/Client5.private.swiftinterface + +// CHECK: import BaseModule +// CHECK-NOT: import ExtensionModule +// CHECK: public class DerivedClass +// CHECK-NOT: init(handler: +// CHECK-NOT: init(nonnullHandler: +// CHECK-NOT: ExtensionType + +// CHECK-PUBLIC: import BaseModule +// CHECK-PUBLIC-NOT: import ExtensionModule +// CHECK-PUBLIC: public class DerivedClass +// CHECK-PUBLIC-NOT: init(handler: +// CHECK-PUBLIC-NOT: init(nonnullHandler: +// CHECK-PUBLIC-NOT: ExtensionType + +// CHECK-SPI-PRIVATE: import BaseModule +// CHECK-SPI-PRIVATE: @_spiOnly import ExtensionModule +// CHECK-SPI-PRIVATE: public class DerivedClass +// CHECK-SPI-PRIVATE: init!(handler: +// CHECK-SPI-PRIVATE: init!(nonnullHandler +// CHECK-SPI-PRIVATE: ExtensionType + +// CHECK-SPI-PUBLIC: import BaseModule +// CHECK-SPI-PUBLIC-NOT: @_spiOnly import ExtensionModule +// CHECK-SPI-PUBLIC-NOT: import ExtensionModule +// CHECK-SPI-PUBLIC: public class DerivedClass +// CHECK-SPI-PUBLIC-NOT: init(handler: +// CHECK-SPI-PUBLIC-NOT: init(nonnullHandler: +// CHECK-SPI-PUBLIC-NOT: ExtensionType + +// CHECK-PACKAGE-PRIVATE: import BaseModule +// CHECK-PACKAGE-PRIVATE-NOT: import ExtensionModule +// CHECK-PACKAGE-PRIVATE: public class DerivedClass +// CHECK-PACKAGE-PRIVATE-NOT: init(handler: +// CHECK-PACKAGE-PRIVATE-NOT: init(nonnullHandler: +// CHECK-PACKAGE-PRIVATE-NOT: ExtensionType + +// CHECK-PACKAGE: import BaseModule +// CHECK-PACKAGE: package import ExtensionModule +// CHECK-PACKAGE: public class DerivedClass +// CHECK-PACKAGE: init!(handler: +// CHECK-PACKAGE: init!(nonnullHandler +// CHECK-PACKAGE: ExtensionType + +// CHECK-PRIVATE: import BaseModule +// CHECK-PRIVATE-NOT: import ExtensionModule +// CHECK-PRIVATE: public class DerivedClass +// CHECK-PRIVATE-NOT: init(handler: +// CHECK-PRIVATE-NOT: init(nonnullHandler: +// CHECK-PRIVATE-NOT: ExtensionType + +//--- Inputs/implementation-only-init/Base.h +@import Foundation; +@interface BaseClass : NSObject +- (instancetype)initWithInfo:(id)info responder:(id)responder; +@end + +//--- Inputs/implementation-only-init/module.modulemap +module BaseModule { + header "Base.h" + export * +} + +module ExtensionModule { + header "Extension.h" + export * +} + +//--- Inputs/implementation-only-init/Extension.h +@import Foundation; +#import "Base.h" +@interface ExtensionType : NSObject +@end +@interface BaseClass (Extension) +- (instancetype)initWithHandler:(ExtensionType *)handler responder:(id)responder; +- (instancetype)initWithNonnullHandler:(ExtensionType * _Nonnull)handler; +@end + +//--- test.swift +import BaseModule +internal import ExtensionModule +public class DerivedClass : BaseClass { +} + +//--- test2.swift +import BaseModule +@_implementationOnly import ExtensionModule +public class DerivedClass : BaseClass { +} + +//--- test3.swift +import BaseModule +@_spiOnly import ExtensionModule +public class DerivedClass : BaseClass { +} + +//--- test4.swift +import BaseModule +package import ExtensionModule +public class DerivedClass : BaseClass { +} + +//--- test5.swift +import BaseModule +private import ExtensionModule +public class DerivedClass : BaseClass { +}