-
Notifications
You must be signed in to change notification settings - Fork 10.5k
SPI support in Swift #29810
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
SPI support in Swift #29810
Conversation
@swift-ci Please test |
Build failed |
@swift-ci please test Windows platform |
@swift-ci Please smoke test |
@swift-ci please test Windows platform |
1 similar comment
@swift-ci please test Windows platform |
The Windows test fails on network issues, it passed the first time. @compnerd you can trigger it again if you want. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like SPI is not inherited from the parent context (type, or extension etc). Is this intended? Maybe it would help if the various places that directly checked for an SPI attribute on the decl were modified to evaluate a request instead, and the request could look at the parent context.
include/swift/AST/SourceFile.h
Outdated
StringRef filename; | ||
|
||
// Names of explicitly imported SPIs. | ||
SmallVector<Identifier, 4> spis; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an arena-allocated ArrayRef, just like the ImportedModuleDesc itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, it should now be fixed.
@@ -1378,6 +1378,8 @@ ERROR(projection_value_property_not_identifier,none, | |||
// Access control | |||
ERROR(attr_access_expected_set,none, | |||
"expected 'set' as subject of '%0' modifier", (StringRef)) | |||
ERROR(attr_access_expected_spi_name,none, | |||
"expected an SPI identifier as subject of the @_spi attribute", ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tend to quote attribute names in diagnostics
lib/Sema/TypeCheckAttr.cpp
Outdated
|
||
auto VD = dyn_cast<ValueDecl>(D); | ||
if (!VD) { | ||
diagnose(attr->getLocation(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this already handled as part of the definition in Attr.def?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, we only need the check for the access level of the decl.
lib/Sema/TypeCheckAccess.cpp
Outdated
void AccessControlCheckerBase::checkTypeAccessImpl( | ||
Type type, TypeRepr *typeRepr, AccessScope contextAccessScope, | ||
const DeclContext *useDC, bool mayBeInferred, | ||
const DeclContext *useDC, bool mayBeInferred, bool fromSPI, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps 'fromSPI' should be an enum?
lib/AST/Module.cpp
Outdated
@@ -549,6 +549,11 @@ void ModuleDecl::lookupObjCMethods( | |||
FORWARD(lookupObjCMethods, (selector, results)); | |||
} | |||
|
|||
void ModuleDecl::lookupImportedSPIs(const ModuleDecl *importedModule, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the module variant of this called anywhere? Generally, imports in other source files don't affect behavior in a source file. So the decl checker should always be calling this on a SourceFile and not the entire module. Also, FORWARD is O(n) in the number of source files -- this can be slow if you're calling it a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lookupImportedSPIs
is called on modules when printing the textual Swift interface and for serialization (including at merge-modules), so it's not called often. I'm not sure we could keep in only on the source file in this case.
However, the SPI decl checks use isImportedAsSPI
which is only on source files and it calls only SourceFile:: lookupImportedSPIs
.
lib/Serialization/ModuleFile.cpp
Outdated
@@ -2531,6 +2531,11 @@ void ModuleFile::lookupObjCMethods( | |||
} | |||
} | |||
|
|||
void ModuleFile::lookupImportedSPIs(const ModuleDecl *importedModule, | |||
SmallVectorImpl<Identifier> &spis) const { | |||
// TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably this won't be needed at all, so maybe lookupImportedSPIs should be a method on SourceFile only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this one when printing the private textual interface at the merge-module phase as it needs all the SPI imported from all partial modules.
Updated to rebase on master and apply Slava's comments. |
@swift-ci please smoke test |
@swift-ci please clean test Windows platform |
VD->getAttrs().hasAttribute<SPIAccessControlAttr>() && | ||
!useSF->isImportedAsSPI(VD)) | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a duplication. Could you refactor them to a common function?
|
||
for (auto attr : targetDecl->getAttrs().getAttributes<SPIAccessControlAttr>()) | ||
for (auto declSPI : attr->getSPINames()) | ||
for (auto importedSPI : importedSpis) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, three nested loops.. can we use Set
somewhere to avoid iterating?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan on moving the logic collecting the SPI groups declared on a decl to a request in order to cover what Slava pointed out with decls inheriting SPI from their parent. With it I can probably add a function for the previous comment too.
include/swift/AST/FileUnit.h
Outdated
/// Find all SPI imported from \p importedModule by this module, collecting | ||
/// their identifiers in \p spis. | ||
virtual void lookupImportedSPIs(const ModuleDecl *importedModule, | ||
SmallVectorImpl<Identifier> &spis) const {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name is kind of confusing. Can we change it to something like lookupImportedSPIKinds
or lookupImportedSPINotions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the ambiguous naming for an "SPI name" to "SPI group" instead.
include/swift/AST/SourceFile.h
Outdated
StringRef filename; | ||
|
||
// Names of explicitly imported SPIs. | ||
ArrayRef<Identifier> spis; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we rename this to spiKinds
? spis
sounds like these are the decl identifiers of decls marked as SPI.
|
||
// Always print SPI decls if `PrintSPIs`. | ||
if (options.PrintSPIs && | ||
VD->getAttrs().hasAttribute<SPIAccessControlAttr>()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this check necessary? It should be fine if we always print SPI attributes and they will be skipped (because decls are skipped) if we print non-spi version of the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPI decls would be skipped because there formal access is considered to be internal here, so we have to force skip them. It’s a bit backwards because of the recent change from the default internal to public, I’ll have to come back to clean this.
lib/Sema/TypeCheckAccess.cpp
Outdated
// diag::conformance_from_implementation_only_module. | ||
enum class Reason : unsigned { | ||
General, | ||
ExtensionWithPublicMembers, | ||
ExtensionWithConditionalConformances | ||
}; | ||
enum class HiddenImportKind : unsigned { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint8_t
should be sufficient.
A request: Please diagnose |
@swift-ci Please smoke test |
@xymus, could you please clarify if the following is per-spec? I know IDE behavior is beyond the purview of this work, but I can't find much documentation. Two Swift packages, one is fetched via source control and another is a local clone. Both are added to an Xcode project via the Xcode/SPM package interface. The local clone will expose and allow for |
@_spi does not seem to limit visibility of inherited initialisers in classes when protection is added on the initialiser in a super class and subclass does not override it |
Add experimental support to define System Programming Interfaces (SPI) in Swift. An SPI is a kind of API targetted at specific clients and that is hidden by default. In practice, a Swift library developer could mark decls as SPI if they are reserved for a closely-related library or client, or if they are experimental and may be modified without warning in the next version of the library.
Definition and importation of SPIs is declared with the
@_spi
attribute. A public decl marked with@_spi(SPIName)
is usable only from within the same module and by clients that imports the module with the compatible@_spi(SPIName)
attribute.In the following example,
MyLib
defines a function under the SPI namedExperimental
.The SPI function
newExperimentalService
is hidden from clients that imports the module normally.However, clients that imports
MyLib
and its SPIExperimental
have access to all decls with the attribute@_spi(Experimental)
declared inMyLib
. This is a way for the library clients to opt-in using the SPI.The developers of a binary framework can restrict access to its SPIs by distributing the new private Swift textual interface file (.private.swiftinterface) only to the authorized clients. This file contains both the public API and all SPIs, it is generated next to the public textual interface (.swiftinterface). The compiler prefers to load the private textual interface and falls back on the public one if the private is not found. Generating the private file is requested by the option
-emit-private-module-interface-path
.This is a first partial support for SPI in Swift. A few things still need work, including the diagnostics that is not always aware of the SPI restriction and reports SPI decls as being internal.
We might want to find a better syntax for SPI attributes that can hold a list of names. The current underlying implementation already supports a list of name per attribute but it’s not reflected in the syntax.