Skip to content

Commit 6eacb40

Browse files
authored
Merge pull request #66261 from apple/es-spi
Add implicit SPI import feature
2 parents b69004d + 32f53d1 commit 6eacb40

File tree

5 files changed

+140
-6
lines changed

5 files changed

+140
-6
lines changed

lib/AST/Module.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,13 +3304,23 @@ void SourceFile::lookupImportedSPIGroups(
33043304
}
33053305
}
33063306

3307+
bool shouldImplicitImportAsSPI(ArrayRef<Identifier> spiGroups) {
3308+
for (auto group : spiGroups) {
3309+
if (group.empty())
3310+
return true;
3311+
}
3312+
return false;
3313+
}
3314+
33073315
bool SourceFile::isImportedAsSPI(const ValueDecl *targetDecl) const {
33083316
auto targetModule = targetDecl->getModuleContext();
33093317
llvm::SmallSetVector<Identifier, 4> importedSPIGroups;
33103318

33113319
// Objective-C SPIs are always imported implicitly.
33123320
if (targetDecl->hasClangNode())
33133321
return !targetDecl->getSPIGroups().empty();
3322+
if (shouldImplicitImportAsSPI(targetDecl->getSPIGroups()))
3323+
return true;
33143324

33153325
lookupImportedSPIGroups(targetModule, importedSPIGroups);
33163326
if (importedSPIGroups.empty())
@@ -3345,13 +3355,15 @@ bool SourceFile::importsModuleAsWeakLinked(const ModuleDecl *module) const {
33453355

33463356
bool ModuleDecl::isImportedAsSPI(const SpecializeAttr *attr,
33473357
const ValueDecl *targetDecl) const {
3358+
auto declSPIGroups = attr->getSPIGroups();
3359+
if (shouldImplicitImportAsSPI(declSPIGroups))
3360+
return true;
3361+
33483362
auto targetModule = targetDecl->getModuleContext();
33493363
llvm::SmallSetVector<Identifier, 4> importedSPIGroups;
33503364
lookupImportedSPIGroups(targetModule, importedSPIGroups);
33513365
if (importedSPIGroups.empty()) return false;
33523366

3353-
auto declSPIGroups = attr->getSPIGroups();
3354-
33553367
for (auto declSPI : declSPIGroups)
33563368
if (importedSPIGroups.count(declSPI))
33573369
return true;
@@ -3361,6 +3373,9 @@ bool ModuleDecl::isImportedAsSPI(const SpecializeAttr *attr,
33613373

33623374
bool ModuleDecl::isImportedAsSPI(Identifier spiGroup,
33633375
const ModuleDecl *fromModule) const {
3376+
if (shouldImplicitImportAsSPI({spiGroup}))
3377+
return true;
3378+
33643379
llvm::SmallSetVector<Identifier, 4> importedSPIGroups;
33653380
lookupImportedSPIGroups(fromModule, importedSPIGroups);
33663381
if (importedSPIGroups.empty())

lib/Parse/ParseDecl.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ bool Parser::parseSpecializeAttributeArguments(
914914
}
915915
}
916916
if (ParamLabel == "spi") {
917-
if (!Tok.is(tok::identifier)) {
917+
if (!Tok.isIdentifierOrUnderscore()) {
918918
diagnose(Tok.getLoc(), diag::attr_specialize_expected_spi_name);
919919
consumeToken();
920920
return false;
@@ -2840,7 +2840,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
28402840

28412841
SmallVector<Identifier, 4> spiGroups;
28422842

2843-
if (!Tok.is(tok::identifier) ||
2843+
if (!Tok.isIdentifierOrUnderscore() ||
28442844
Tok.isContextualKeyword("set")) {
28452845
diagnose(getEndOfPreviousLoc(), diag::attr_access_expected_spi_name);
28462846
consumeToken();
@@ -2849,6 +2849,11 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
28492849
}
28502850

28512851
auto text = Tok.getText();
2852+
// An spi group name can be '_' as in @_spi(_), a specifier for implicit import of the SPI.
2853+
// '_' in source code is represented as an empty identifier in AST so match the behavior
2854+
// here for consistency
2855+
if (Tok.is(tok::kw__))
2856+
text = StringRef();
28522857
spiGroups.push_back(Context.getIdentifier(text));
28532858
consumeToken();
28542859

lib/Serialization/Serialization.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2677,7 +2677,10 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
26772677

26782678
SmallVector<IdentifierID, 4> spis;
26792679
for (auto spi : theAttr->getSPIGroups()) {
2680-
assert(!spi.empty() && "Empty SPI name");
2680+
// SPI group name in source code can be '_', a specifier that allows
2681+
// implicit import of the SPI. It gets converted to to an empty identifier
2682+
// during parsing to match the existing AST node representation. An empty
2683+
// identifier is printed as '_' at serialization.
26812684
spis.push_back(S.addDeclBaseNameRef(spi));
26822685
}
26832686

test/SPI/implicit_spi_import.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
5+
// RUN: -module-name Lib -swift-version 5 -I %t \
6+
// RUN: -enable-library-evolution \
7+
// RUN: -emit-module-path %t/Lib.swiftmodule \
8+
// RUN: -emit-module-interface-path %t/Lib.swiftinterface \
9+
// RUN: -emit-private-module-interface-path %t/Lib.private.swiftinterface
10+
11+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientA.swift -I %t
12+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientB.swift -I %t
13+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientC.swift -I %t
14+
15+
// RUN: rm %t/Lib.swiftmodule
16+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientA.swift -I %t
17+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientB.swift -I %t
18+
// RUN: %target-swift-frontend -typecheck -verify %t/ClientC.swift -I %t
19+
20+
// RUN: %target-swift-frontend -emit-module %t/ClientA.swift \
21+
// RUN: -module-name ClientA -swift-version 5 -I %t \
22+
// RUN: -enable-library-evolution \
23+
// RUN: -emit-module-interface-path %t/ClientA.swiftinterface \
24+
// RUN: -emit-private-module-interface-path %t/ClientA.private.swiftinterface
25+
// RUN: %FileCheck %s --check-prefix CHECK-A < %t/ClientA.private.swiftinterface
26+
// CHECK-A-NOT: @_spi(_) import Lib
27+
// CHECK-A: import Lib
28+
// CHECK-A: @_spi(_) public func useImplicit() -> Lib._Klass
29+
30+
// RUN: %target-swift-frontend -emit-module %t/ClientB.swift \
31+
// RUN: -module-name ClientB -swift-version 5 -I %t \
32+
// RUN: -enable-library-evolution \
33+
// RUN: -emit-module-interface-path %t/ClientB.swiftinterface \
34+
// RUN: -emit-private-module-interface-path %t/ClientB.private.swiftinterface
35+
// RUN: %FileCheck %s --check-prefix CHECK-B < %t/ClientB.private.swiftinterface
36+
// CHECK-B-NOT: @_spi(_) @_spi(core) import Lib
37+
// CHECK-B-NOT: @_spi(core) @_spi(_) import Lib
38+
// CHECK-B: @_spi(core) import Lib
39+
// CHECK-B: @_spi(_) public func useImplicit() -> Lib._Klass
40+
// CHECK-B: @_spi(core) public func useSPICore() -> Lib.CoreStruct
41+
42+
// RUN: %target-swift-frontend -emit-module %t/ClientZ.swift \
43+
// RUN: -module-name ClientZ -swift-version 5 -I %t \
44+
// RUN: -enable-library-evolution \
45+
// RUN: -emit-module-interface-path %t/ClientZ.swiftinterface \
46+
// RUN: -emit-private-module-interface-path %t/ClientZ.private.swiftinterface
47+
// RUN: %FileCheck %s --check-prefix CHECK-Z < %t/ClientZ.private.swiftinterface
48+
// CHECK-Z: @_spi(_) import Lib
49+
// CHECK-Z: @_spi(_) public func useImplicit() -> Lib._Klass
50+
51+
52+
//--- Lib.swift
53+
54+
@_spi(core)
55+
public struct CoreStruct {
56+
public init() {}
57+
}
58+
59+
@_spi(_)
60+
public class _Klass {
61+
public init() {}
62+
}
63+
64+
public protocol APIProtocol {
65+
}
66+
67+
68+
//--- ClientA.swift
69+
70+
import Lib
71+
72+
@_spi(_)
73+
public func useImplicit() -> _Klass { return _Klass() }
74+
75+
public func useMain() -> APIProtocol? { return nil }
76+
77+
78+
//--- ClientB.swift
79+
80+
@_spi(core) import Lib
81+
82+
@_spi(_)
83+
public func useImplicit() -> _Klass { return _Klass() }
84+
85+
@_spi(core)
86+
public func useSPICore() -> CoreStruct { return CoreStruct() }
87+
88+
public func useMain() -> APIProtocol? { return nil }
89+
90+
91+
//--- ClientC.swift
92+
93+
import Lib
94+
95+
public func useImplicit() -> _Klass { return _Klass() } // expected-error{{cannot use class '_Klass' here; it is an SPI imported from 'Lib'}}
96+
97+
@_spi(core)
98+
public func useSPICore() -> CoreStruct { return CoreStruct() } // expected-error{{cannot find type 'CoreStruct' in scope}}
99+
100+
public func useMain() -> APIProtocol? { return nil }
101+
102+
103+
//--- ClientZ.swift
104+
105+
@_spi(_) import Lib
106+
107+
@_spi(_)
108+
public func useImplicit() -> _Klass { return _Klass() }
109+
110+
public func useMain() -> APIProtocol? { return nil }

test/SPI/local_spi_decls.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
// SPI declarations
1010
@_spi(MySPI) public func spiFunc() {}
1111
@_spi(+) public func invalidSPIName() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
12-
@_spi(🤔) public func emojiNamedSPI() {}
12+
@_spi(🤔) public func emojiNamedSPI() {} // OK
13+
@_spi(_) public func underscoreNamedSPI() {} // OK
1314
@_spi() public func emptyParensSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
1415
@_spi(set) public func keywordSPI() {} // expected-error {{expected an SPI identifier as subject of the '@_spi' attribute}}
1516

0 commit comments

Comments
 (0)