Skip to content

Commit 0ba6c49

Browse files
committed
Add @_implementationOnly
This is an attribute that gets put on an import in library FooKit to keep it from being a requirement to import FooKit. It's not checked at all, meaning that in this form it is up to the author of FooKit to make sure nothing in its API or ABI depends on the implementation-only dependency. There's also no debugging support here (debugging FooKit /should/ import the implementation-only dependency if it's present). The goal is to get to a point where it /can/ be checked, i.e. FooKit developers are prevented from writing code that would rely on FooKit's implementation-only dependency being present when compiling clients of FooKit. But right now it's not. rdar://problem/48985979
1 parent 9ed3fe0 commit 0ba6c49

36 files changed

+336
-58
lines changed

include/swift/AST/Attr.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ DECL_ATTR(_private, PrivateImport,
390390
SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient,
391391
OnVar | OnSubscript | OnAbstractFunction | UserInaccessible,
392392
83)
393+
SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly,
394+
OnImport | UserInaccessible,
395+
84)
393396

394397
#undef TYPE_ATTR
395398
#undef DECL_ATTR_ALIAS

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,9 @@ ERROR(module_not_testable,Fatal,
765765
ERROR(module_not_compiled_for_private_import,none,
766766
"module %0 was not compiled for private import", (Identifier))
767767

768+
ERROR(import_implementation_cannot_be_exported,none,
769+
"module %0 cannot be both exported and implementation-only", (Identifier))
770+
768771

769772
// Operator decls
770773
ERROR(ambiguous_operator_decls,none,

include/swift/AST/Module.h

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,12 @@ class ModuleDecl : public DeclContext, public TypeDecl {
420420

421421
/// \sa getImportedModules
422422
enum class ImportFilterKind {
423+
/// Include imports declared with `@_exported`.
423424
Public = 1 << 0,
424-
Private = 1 << 1
425+
/// Include "regular" imports with no special annotation.
426+
Private = 1 << 1,
427+
/// Include imports declared with `@_implementationOnly`.
428+
ImplementationOnly = 1 << 2
425429
};
426430
/// \sa getImportedModules
427431
using ImportFilter = OptionSet<ImportFilterKind>;
@@ -892,23 +896,29 @@ class SourceFile final : public FileUnit {
892896
/// This source file has access to private declarations in the imported
893897
/// module.
894898
PrivateImport = 0x4,
899+
900+
/// The imported module is an implementation detail of this file and should
901+
/// not be required to be present if the main module is ever imported
902+
/// elsewhere.
903+
///
904+
/// Mutually exclusive with Exported.
905+
ImplementationOnly = 0x8
895906
};
896907

897908
/// \see ImportFlags
898909
using ImportOptions = OptionSet<ImportFlags>;
899910

900-
typedef std::pair<ImportOptions, StringRef> ImportOptionsAndFilename;
901-
902911
struct ImportedModuleDesc {
903912
ModuleDecl::ImportedModule module;
904913
ImportOptions importOptions;
905914
StringRef filename;
906915

907-
ImportedModuleDesc(ModuleDecl::ImportedModule module, ImportOptions options)
908-
: module(module), importOptions(options) {}
909916
ImportedModuleDesc(ModuleDecl::ImportedModule module, ImportOptions options,
910-
StringRef filename)
911-
: module(module), importOptions(options), filename(filename) {}
917+
StringRef filename = {})
918+
: module(module), importOptions(options), filename(filename) {
919+
assert(!(importOptions.contains(ImportFlags::Exported) &&
920+
importOptions.contains(ImportFlags::ImplementationOnly)));
921+
}
912922
};
913923

914924
private:

include/swift/Basic/OptionSet.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ class OptionSet {
8787
return !static_cast<bool>(set - *this);
8888
}
8989

90+
/// Check if this option set contains the exact same options as the given set.
91+
bool containsOnly(OptionSet set) {
92+
return Storage == set.Storage;
93+
}
94+
95+
// '==' and '!=' are deliberately not defined because they provide a pitfall
96+
// where someone might use '==' but really want 'contains'. If you actually
97+
// want '==' behavior, use 'containsOnly'.
98+
9099
/// Produce the union of two option sets.
91100
friend OptionSet operator|(OptionSet lhs, OptionSet rhs) {
92101
return OptionSet(lhs.Storage | rhs.Storage);
@@ -114,7 +123,7 @@ class OptionSet {
114123
return OptionSet(lhs.Storage & ~rhs.Storage);
115124
}
116125

117-
/// Produce the intersection of two option sets.
126+
/// Produce the difference of two option sets.
118127
friend OptionSet &operator-=(OptionSet &lhs, OptionSet rhs) {
119128
lhs.Storage &= ~rhs.Storage;
120129
return lhs;

include/swift/Serialization/ModuleFile.h

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,48 @@ class ModuleFile
135135
const StringRef RawPath;
136136

137137
private:
138-
unsigned IsExported : 1;
138+
using ImportFilterKind = ModuleDecl::ImportFilterKind;
139+
const unsigned RawImportControl : 2;
139140
const unsigned IsHeader : 1;
140141
const unsigned IsScoped : 1;
141142

142-
Dependency(StringRef path, bool isHeader, bool exported, bool isScoped)
143-
: RawPath(path), IsExported(exported), IsHeader(isHeader),
144-
IsScoped(isScoped) {}
143+
static unsigned rawControlFromKind(ImportFilterKind importKind) {
144+
return llvm::countTrailingZeros(static_cast<unsigned>(importKind));
145+
}
146+
ImportFilterKind getImportControl() const {
147+
return static_cast<ImportFilterKind>(1 << RawImportControl);
148+
}
149+
150+
Dependency(StringRef path, bool isHeader, ImportFilterKind importControl,
151+
bool isScoped)
152+
: RawPath(path), RawImportControl(rawControlFromKind(importControl)),
153+
IsHeader(isHeader), IsScoped(isScoped) {
154+
assert(llvm::countPopulation(static_cast<unsigned>(importControl)) == 1 &&
155+
"must be a particular filter option, not a bitset");
156+
assert(getImportControl() == importControl && "not enough bits");
157+
}
145158

146159
public:
147-
Dependency(StringRef path, bool exported, bool isScoped)
148-
: Dependency(path, false, exported, isScoped) {}
160+
Dependency(StringRef path, ImportFilterKind importControl, bool isScoped)
161+
: Dependency(path, false, importControl, isScoped) {}
149162

150163
static Dependency forHeader(StringRef headerPath, bool exported) {
151-
return Dependency(headerPath, true, exported, false);
164+
auto importControl = exported ? ImportFilterKind::Public
165+
: ImportFilterKind::Private;
166+
return Dependency(headerPath, true, importControl, false);
152167
}
153168

154169
bool isLoaded() const {
155170
return Import.second != nullptr;
156171
}
157172

158-
bool isExported() const { return IsExported; }
173+
bool isExported() const {
174+
return getImportControl() == ImportFilterKind::Public;
175+
}
176+
bool isImplementationOnly() const {
177+
return getImportControl() == ImportFilterKind::ImplementationOnly;
178+
}
179+
159180
bool isHeader() const { return IsHeader; }
160181
bool isScoped() const { return IsScoped; }
161182

include/swift/Serialization/ModuleFormat.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5252
/// describe what change you made. The content of this comment isn't important;
5353
/// it just ensures a conflict if two people change the module format.
5454
/// Don't worry about adhering to the 80-column limit for this line.
55-
const uint16_t SWIFTMODULE_VERSION_MINOR = 479; // stored property default arg
55+
const uint16_t SWIFTMODULE_VERSION_MINOR = 480; // Last change: import control
5656

5757
using DeclIDField = BCFixed<31>;
5858

@@ -452,14 +452,26 @@ enum class EnumElementRawValueKind : uint8_t {
452452
/// TODO: Float, string, char, etc.
453453
};
454454

455+
using EnumElementRawValueKindField = BCFixed<4>;
456+
455457
// These IDs must \em not be renumbered or reordered without incrementing
456458
// the module version.
457459
enum class ResilienceExpansion : uint8_t {
458460
Minimal = 0,
459461
Maximal,
460462
};
461463

462-
using EnumElementRawValueKindField = BCFixed<4>;
464+
// These IDs must \em not be renumbered or reordered without incrementing
465+
// the module version.
466+
enum class ImportControl : uint8_t {
467+
/// `import FooKit`
468+
Normal = 0,
469+
/// `@_exported import FooKit`
470+
Exported,
471+
/// `@_uncheckedImplementationOnly import FooKit`
472+
ImplementationOnly
473+
};
474+
using ImportControlField = BCFixed<2>;
463475

464476
/// The various types of blocks that can occur within a serialized Swift
465477
/// module.
@@ -636,8 +648,8 @@ namespace input_block {
636648

637649
using ImportedModuleLayout = BCRecordLayout<
638650
IMPORTED_MODULE,
639-
BCFixed<1>, // exported?
640-
BCFixed<1>, // scoped?
651+
ImportControlField, // import kind
652+
BCFixed<1>, // scoped?
641653
BCBlob // module name, with submodule path pieces separated by \0s.
642654
// If the 'scoped' flag is set, the final path piece is an access
643655
// path within the module.

lib/AST/Module.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,16 +1020,18 @@ void
10201020
SourceFile::getImportedModules(SmallVectorImpl<ModuleDecl::ImportedModule> &modules,
10211021
ModuleDecl::ImportFilter filter) const {
10221022
assert(ASTStage >= Parsed || Kind == SourceFileKind::SIL);
1023+
assert(filter && "no imports requested?");
10231024
for (auto desc : Imports) {
1024-
if (desc.importOptions.contains(ImportFlags::Exported)) {
1025-
if (!filter.contains(ModuleDecl::ImportFilterKind::Public))
1026-
continue;
1027-
} else {
1028-
if (!filter.contains(ModuleDecl::ImportFilterKind::Private))
1029-
continue;
1030-
}
1025+
ModuleDecl::ImportFilterKind requiredKind;
1026+
if (desc.importOptions.contains(ImportFlags::Exported))
1027+
requiredKind = ModuleDecl::ImportFilterKind::Public;
1028+
else if (desc.importOptions.contains(ImportFlags::ImplementationOnly))
1029+
requiredKind = ModuleDecl::ImportFilterKind::ImplementationOnly;
1030+
else
1031+
requiredKind = ModuleDecl::ImportFilterKind::Private;
10311032

1032-
modules.push_back(desc.module);
1033+
if (filter.contains(requiredKind))
1034+
modules.push_back(desc.module);
10331035
}
10341036
}
10351037

@@ -1275,7 +1277,11 @@ forAllImportedModules(ModuleDecl *topLevel, ModuleDecl::AccessPathTy thisPath,
12751277
ModuleDecl::ImportFilter filter = ModuleDecl::ImportFilterKind::Public;
12761278
if (!respectVisibility)
12771279
filter |= ModuleDecl::ImportFilterKind::Private;
1278-
topLevel->getImportedModules(stack, filter);
1280+
1281+
ModuleDecl::ImportFilter topLevelFilter = filter;
1282+
if (!respectVisibility)
1283+
topLevelFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
1284+
topLevel->getImportedModules(stack, topLevelFilter);
12791285

12801286
// Make sure the top-level module is first; we want pre-order-ish traversal.
12811287
AccessPathTy overridingPath;
@@ -1326,9 +1332,11 @@ bool FileUnit::forAllVisibleModules(
13261332

13271333
if (auto SF = dyn_cast<SourceFile>(this)) {
13281334
// Handle privately visible modules as well.
1329-
// FIXME: Should this apply to all FileUnits?
1335+
ModuleDecl::ImportFilter importFilter;
1336+
importFilter |= ModuleDecl::ImportFilterKind::Private;
1337+
importFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
13301338
SmallVector<ModuleDecl::ImportedModule, 4> imports;
1331-
SF->getImportedModules(imports, ModuleDecl::ImportFilterKind::Private);
1339+
SF->getImportedModules(imports, importFilter);
13321340
for (auto importPair : imports)
13331341
if (!importPair.second->forAllVisibleModules(importPair.first, fn))
13341342
return false;

lib/AST/UnqualifiedLookup.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,8 +1044,12 @@ void UnqualifiedLookupFactory::recordDependencyOnTopLevelName(
10441044
void UnqualifiedLookupFactory::addImportedResults(DeclContext *const dc) {
10451045
// Add private imports to the extra search list.
10461046
SmallVector<ModuleDecl::ImportedModule, 8> extraImports;
1047-
if (auto FU = dyn_cast<FileUnit>(dc))
1048-
FU->getImportedModules(extraImports, ModuleDecl::ImportFilterKind::Private);
1047+
if (auto FU = dyn_cast<FileUnit>(dc)) {
1048+
ModuleDecl::ImportFilter importFilter;
1049+
importFilter |= ModuleDecl::ImportFilterKind::Private;
1050+
importFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
1051+
FU->getImportedModules(extraImports, importFilter);
1052+
}
10491053

10501054
using namespace namelookup;
10511055
SmallVector<ValueDecl *, 8> CurModuleResults;

lib/ClangImporter/ClangImporter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3203,6 +3203,11 @@ ModuleDecl *ClangModuleUnit::getAdapterModule() const {
32033203
void ClangModuleUnit::getImportedModules(
32043204
SmallVectorImpl<ModuleDecl::ImportedModule> &imports,
32053205
ModuleDecl::ImportFilter filter) const {
3206+
// Bail out if we /only/ want ImplementationOnly imports; Clang modules never
3207+
// have any of these.
3208+
if (filter.containsOnly(ModuleDecl::ImportFilterKind::ImplementationOnly))
3209+
return;
3210+
32063211
if (filter.contains(ModuleDecl::ImportFilterKind::Private))
32073212
if (auto stdlib = owner.getStdlibModule())
32083213
imports.push_back({ModuleDecl::AccessPathTy(), stdlib});

lib/FrontendTool/ImportedModules.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ bool swift::emitImportedModules(ASTContext &Context, ModuleDecl *mainModule,
8282
ModuleDecl::ImportFilter importFilter;
8383
importFilter |= ModuleDecl::ImportFilterKind::Public;
8484
importFilter |= ModuleDecl::ImportFilterKind::Private;
85+
importFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly;
8586

8687
SmallVector<ModuleDecl::ImportedModule, 16> imported;
8788
clangImporter->getImportedHeaderModule()->getImportedModules(

0 commit comments

Comments
 (0)