Skip to content
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
14 changes: 14 additions & 0 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExtensionDecl>(where.getDeclContext());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't handle a case like this:

@_spi_available(...)
public struct Outer {
  public struct Inner {
  
  }
}

extension Outer.Inner {
  // ...
}

It's probably not worth addressing now, though. We should aim to support that with a more holistic fix though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we'd need to properly propagate the @_spi_available-ness across contexts. That will need a wider fix as the current mechanism for it is mixed in with the normal SPI and probably shouldn't be.

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 :
Expand Down
60 changes: 60 additions & 0 deletions test/SPI/spi_and_spi_available.swift
Original file line number Diff line number Diff line change
@@ -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()
}