-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add @_private(from: "SourceFile.swift") imports #20428
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
Conversation
A module compiled with `-enable-private-imports` allows other modules to import private declarations if the importing source file uses an ``@_private(from: "SourceFile.swift") import statement. rdar://29318654
@swift-ci Please test |
Build failed |
Build failed |
Please test with following PR: @swift-ci Please test |
include/swift/AST/Module.h
Outdated
Testable = 0x2, | ||
|
||
/// This source file has access to private declarations in the imported | ||
/// moduled. |
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.
Typo: "moduled".
include/swift/AST/Module.h
Outdated
@@ -857,7 +870,9 @@ class SourceFile final : public FileUnit { | |||
/// This is the list of modules that are imported by this module. | |||
/// | |||
/// This is filled in by the Name Binding phase. | |||
ArrayRef<std::pair<ModuleDecl::ImportedModule, ImportOptions>> Imports; | |||
ArrayRef<std::pair<ModuleDecl::ImportedModule, | |||
std::pair<ImportOptions, StringRef>>> |
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 you make a proper type or at least a typedef for this? Or put the StringRef into ImportOptions?
include/swift/AST/Module.h
Outdated
auto it = FilenameForPrivateDecls.find(decl); | ||
if (it == FilenameForPrivateDecls.end()) | ||
return StringRef(); | ||
else return it->second.str(); |
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.
Nitpick: no else
after an early return.
@@ -1224,12 +1242,29 @@ class LoadedFile : public FileUnit { | |||
assert(classof(this) && "invalid kind"); | |||
} | |||
|
|||
/// A map from private/fileprivate decls to the file they were defined in. | |||
llvm::DenseMap<const ValueDecl *, Identifier> FilenameForPrivateDecls; |
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 you explain why this is necessary? It seems like it should be an on-demand on-disk hash table like the private discriminator map.
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 works in the same way as PrivateDiscriminatorsByValue
when we deserialize a value decl we put it in the map.
When we need to determine hasPrivateImport(const swift::ValueDecl *ofDecl)
we check it.
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.
Oops. I didn't realize we did that eagerly for private discriminators too.
…and now I'm working through the reasoning that leads there again: you can't easily look up a decl pointer in a module. My mistake!
@@ -192,6 +192,11 @@ class FrontendOptions { | |||
/// \see ModuleDecl::isTestingEnabled | |||
bool EnableTesting = false; | |||
|
|||
/// Indicates whether we are compiling for testing. |
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.
Comment didn't get updated.
lib/AST/Module.cpp
Outdated
filename = file->getFilenameForPrivateDecl(ofDecl); | ||
} else if (auto *file = dyn_cast<SourceFile>(scope)) { | ||
// Source files cannot be imported from another module. | ||
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.
I don't think you even need to check this.
lib/AST/Module.cpp
Outdated
auto *module = ofDecl->getModuleContext(); | ||
|
||
if (accessLevel == AccessLevel::Internal || | ||
accessLevel == AccessLevel::Public) { |
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.
And Open
doesn't need any of this. Please use a switch
here so that we don't miss cases!
lib/Parse/ParseDecl.cpp
Outdated
|
||
// Parse ':'. | ||
if (Tok.getText() != ":") { |
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 should check the token kind as tok::colon
instead.
lib/Serialization/Serialization.cpp
Outdated
if (M->arePrivateImportsEnabled()) { | ||
auto filename = getFilename(enclosingFile, value); | ||
if (!filename.empty()) { | ||
auto filenameID = addFilename(llvm::sys::path::filename(filename)); |
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 only have to do this if we're starting from a SourceFile, right? So you might as well push this into getFilename
.
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.
You have to do it if we are starting from a source file or if we are merging modules so that we reemit the information.
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.
Sorry, I just meant the llvm::sys::path::filename
part.
#elseif MAIN | ||
|
||
@_private(sourceFile: "private_import.swift") import private_import | ||
@_private(sourceFile: "private_import.swift") import client |
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.
Don't forget some negative test cases: importing a different file shouldn't give you access. It's probably also worth testing a case where we actually have a naming conflict, both at the top level and for members.
Please test with following PR: @swift-ci Please test |
Please test with following PR: @swift-ci Please test OS X platform |
1 similar comment
Please test with following PR: @swift-ci Please test OS X platform |
@@ -1422,7 +1422,14 @@ bool SourceFile::hasPrivateImport(AccessLevel accessLevel, | |||
importPair.second.first.contains( | |||
ImportFlags::PrivateImport); | |||
}); | |||
case AccessLevel::Open: |
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 probably wasn't supposed to fall through here.
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.
there is a return before case AccessLevel::Open
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.
Oops, sorry. Misleading partial diff!
|
||
extension Base { | ||
// This should not cause a failure. | ||
private func shouldNotBeVisible() {} |
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.
That wouldn't cause a failure anyway; shadowing is always okay. You have to have a conflict within the same module.
Build failed |
Please test with following PR: @swift-ci Please test |
Build failed |
Build failed |
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 is just bikeshedding but perhaps @_private(from: ...)
is better than @_private(sourceFile: ...)
lib/AST/Decl.cpp
Outdated
// @testable/@_private import attributes. | ||
auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()); | ||
if (!useSF) return access; | ||
if (useSF->hasPrivateImport(access, VD)) |
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 you combine hasPrivateImport() with hasTestableImport(), and have it take an access level?
include/swift/AST/Module.h
Outdated
private: | ||
std::unique_ptr<LookupCache> Cache; | ||
LookupCache &getCache() const; | ||
|
||
/// This is the list of modules that are imported by this module. | ||
/// | ||
/// This is filled in by the Name Binding phase. | ||
ArrayRef<std::pair<ModuleDecl::ImportedModule, ImportOptions>> Imports; | ||
ArrayRef<std::pair<ModuleDecl::ImportedModule, ImportOptionsAndFilename>> |
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.
A pair where the second type is again a pair is a bit too much IMHO. Can you just define a struct with three fields instead?
ERROR(attr_private_import_expected_sourcefile, none, | ||
"expected 'sourceFile' in '_private' attribute", ()) | ||
ERROR(attr_private_import_expected_sourcefile_name,none, | ||
"expected a source file name in @_private(sourceFile:)", ()) |
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'm a bit confused, is it @_private(sourceFile:)
or @_private(from:)
(like in the commit message :) )
@swift-ci Please test |
Please test with following PR: @swift-ci Please test |
Build failed |
Build failed |
Please test with following PR: @swift-ci Please test |
Build failed |
Build failed |
Please test with following PR: @swift-ci Please test |
Build failed |
Please test with following PR: @swift-ci Please test |
Build failed |
Build failed |
Build failed |
@@ -3613,7 +3632,8 @@ bool EnumDecl::isFormallyExhaustive(const DeclContext *useDC) const { | |||
// Testably imported enums are exhaustive, on the grounds that only the author | |||
// of the original library can import it testably. | |||
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext())) | |||
if (useSF->hasTestableImport(containingModule)) | |||
if (useSF->hasTestableOrPrivateImport(AccessLevel::Internal, | |||
containingModule)) |
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.
You probably want this one to do private imports too.
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 works for either private or testable imports. the access level is used inside of the function to determine whether we need to check the filename (if it is private or file private).
Using AccessLevel::Internal means we will use the check that checks whether an private or testable import occurred (without checking the filename in the private case).
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.
Uh, hm. But this should be specific to the enum we're asking the question of, right?
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.
You are correct it would make a difference for private imports and private/fileprivate declarations.
What you don't see in the diff is that there is a test before this test that checks if the enum is not public.
// Non-public, non-versioned enums are always exhaustive.
AccessScope accessScope = getFormalAccessScope(/*useDC*/nullptr,
/*respectVersioned*/true);
if (!accessScope.isPublic())
return true;
We end up at useSF->hasTestableOrPrivateImport
only with a public enum.
In this case we end up in the internal/public branch that does not depend on the file.
bool SourceFile::hasTestableOrPrivateImport(
AccessLevel accessLevel, const swift::ValueDecl *ofDecl) const {
auto *module = ofDecl->getModuleContext();
switch (accessLevel) {
case AccessLevel::Internal:
case AccessLevel::Public:
// internal/public access only needs an import marked as @_private. The
// filename does not need to match (and we don't serialize it for such
// decls).
return std::any_of(
Imports.begin(), Imports.end(),
[module](ImportedModuleDesc desc) -> bool {
return desc.module.second == module &&
(desc.importOptions.contains(ImportFlags::PrivateImport) ||
desc.importOptions.contains(ImportFlags::Testable));
});
case AccessLevel::Open:
return true;
case AccessLevel::FilePrivate:
case AccessLevel::Private:
// Fallthrough.
break;
}
Therefore this check is sufficient.
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.
Oof, right. Okay. Too many components depending on each other…
TheModule->getName().str(), | ||
AccessPath, | ||
Request.NeedLeadingDot, | ||
SF.hasTestableOrPrivateImport(AccessLevel::Internal, TheModule), |
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 is interesting. How is code completion going to work here?
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 is NFC compared to before. This was only rewritten to use the SF.hasTestableOrPrivateImport instead of the hasTestableImport function. The outcome should be the same (whatever that is).
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, that's not the problem. What I'm worried about is that the private-imported members either won't show up in code completion, or they will and could incorrectly show up in the cache for that module.
if (auto *SF = dyn_cast<SourceFile>(enclosingFile)) { | ||
return llvm::sys::path::filename(SF->getFilename()); | ||
} else if (auto *LF = dyn_cast<LoadedFile>(enclosingFile)) { | ||
return llvm::sys::path::filename( |
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.
My point was that you can skip this step, because any LoadedFile has already applied it.
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. Thanks. Will fix.
|
||
Base().foo() | ||
// This should not be ambigious. | ||
Base().bar() |
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.
You're still missing a case where it's the same type but different methods in each file.
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.
Will fix.
// RUN: %target-swift-frontend -typecheck -I %t -I %S/Inputs/custom-modules %s -verify | ||
|
||
@_private(sourceFile: "Array.swift") import Swift // expected-error {{module 'Swift' was not compiled for private import}} | ||
@_private(sourceFile: "empty.swift") import empty // no-error |
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.
What happens if I private-import a file that isn't in that module? ("Nothing", I know, but it's probably worth having a test case for it.)
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.
Will fix.
A module compiled with
-enable-private-imports
allows other modules toimport private declarations if the importing source file uses an
``@_private(from: "SourceFile.swift") import statement.
rdar://29318654