From 1adbe37f3584a3028a38e426ced31617c8304c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 9 Sep 2025 16:07:11 -0700 Subject: [PATCH] Sema: Narrow fix to allow `@_spi_available` in extensions Allow referencing an `@_spi_available` decl in extensions to `@_spi_available` types. This is a narrow fix as it should really be handled as part of the context check but that check is currently too permissive. Fow now let's narrowly allow legal code. And then we should look at revisiting the SPI availability logic, separate it from normal SPI and treat more like availability. Adding a test comparing the behavior of `@_spi` with `@_spi_available` to document the current implementation. rdar://159292698 --- lib/Sema/TypeCheckAccess.cpp | 14 +++++++ test/SPI/spi_and_spi_available.swift | 60 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 test/SPI/spi_and_spi_available.swift diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 492aba619df3d..4a844cf7b2e0f 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -2124,7 +2124,21 @@ swift::getDisallowedOriginKind(const Decl *decl, return DisallowedOriginKind::None; } } + + // Allow SPI available use in an extension to an SPI available type. + // This is a narrow workaround for rdar://159292698 as this should be + // handled as part of the `where.isSPI()` above, but that context check + // is currently too permissive. It allows SPI use in SPI available which + // can break swiftinterfaces. The SPI availability logic likely need to be + // separated from normal SPI and treated more like availability. + auto ext = dyn_cast_or_null(where.getDeclContext()); + if (ext) { + auto nominal = ext->getExtendedNominal(); + if (nominal && nominal->isAvailableAsSPI()) + return DisallowedOriginKind::None; + } } + // SPI can only be exported in SPI. return where.getDeclContext()->getParentModule() == M ? DisallowedOriginKind::SPILocal : diff --git a/test/SPI/spi_and_spi_available.swift b/test/SPI/spi_and_spi_available.swift new file mode 100644 index 0000000000000..72c05e70bdd80 --- /dev/null +++ b/test/SPI/spi_and_spi_available.swift @@ -0,0 +1,60 @@ +// RUN: %empty-directory(%t) +// RUN: %target-typecheck-verify-swift \ +// RUN: -enable-library-evolution -swift-version 5 \ +// RUN: -library-level=api -require-explicit-availability=ignore + +// REQUIRES: OS=macosx + +@_spi_available(macOS, introduced: 12.0) @available(iOS 12.0, *) +public struct SPIAvailableType { // expected-note {{struct declared here}} + public init() {} +} + +@_spi(S) +public struct NormalSPIType { + public init() {} +} + +// SPI available in SPI available should be accepted. + +@_spi_available(macOS, introduced: 12.0) @available(iOS 12.0, *) +public struct OtherSPIAvailableType { + public func foo(s: SPIAvailableType) {} +} + +extension OtherSPIAvailableType { + public func bar(s: SPIAvailableType) {} // expected-error {{cannot use struct 'SPIAvailableType' here; it is SPI}} // FIXME We should allow this. +} + +// Normal SPI in normal SPI should be accepted. + +@_spi(S) +public struct OtherNormalSPIType { + public func foo(s: SPIAvailableType) {} +} + +extension OtherNormalSPIType { + public func bar2(s: SPIAvailableType) {} +} + +// Normal SPI in SPI available should be rejected. + +@_spi_available(macOS, introduced: 12.0) @available(iOS 12.0, *) +public func SPIAvailableToSPI(s: NormalSPIType) {} // FIXME This should be an error + +@inlinable +@_spi_available(macOS, introduced: 12.0) @available(iOS 12.0, *) +public func inlinableSPIAvailable() { + let _: NormalSPIType = NormalSPIType() // FIXME There should be many errors here +} + +// SPI available in normal SPI is currently accepted. + +@_spi(S) +public func SPIToSPIAvailable(s: NormalSPIType) {} + +@inlinable +@_spi(S) +public func inlinableSPI() { + let _: SPIAvailableType = SPIAvailableType() +}