Skip to content

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

Merged
merged 7 commits into from
Nov 9, 2018

Conversation

aschwaighofer
Copy link
Contributor

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

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
@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 8, 2018

Build failed
Swift Test Linux Platform
Git Sha - 963c64e

@swift-ci
Copy link
Contributor

swift-ci commented Nov 8, 2018

Build failed
Swift Test OS X Platform
Git Sha - 963c64e

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

Testable = 0x2,

/// This source file has access to private declarations in the imported
/// moduled.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "moduled".

@@ -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>>>
Copy link
Contributor

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?

auto it = FilenameForPrivateDecls.find(decl);
if (it == FilenameForPrivateDecls.end())
return StringRef();
else return it->second.str();
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.
Copy link
Contributor

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.

filename = file->getFilenameForPrivateDecl(ofDecl);
} else if (auto *file = dyn_cast<SourceFile>(scope)) {
// Source files cannot be imported from another module.
return false;
Copy link
Contributor

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.

auto *module = ofDecl->getModuleContext();

if (accessLevel == AccessLevel::Internal ||
accessLevel == AccessLevel::Public) {
Copy link
Contributor

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!


// Parse ':'.
if (Tok.getText() != ":") {
Copy link
Contributor

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.

if (M->arePrivateImportsEnabled()) {
auto filename = getFilename(enclosingFile, value);
if (!filename.empty()) {
auto filenameID = addFilename(llvm::sys::path::filename(filename));
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

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.

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test OS X platform

1 similar comment
@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@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:
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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() {}
Copy link
Contributor

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.

@swift-ci
Copy link
Contributor

swift-ci commented Nov 8, 2018

Build failed
Swift Test OS X Platform
Git Sha - 963c64e

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 8, 2018

Build failed
Swift Test Linux Platform
Git Sha - e4f4dfc

@swift-ci
Copy link
Contributor

swift-ci commented Nov 8, 2018

Build failed
Swift Test OS X Platform
Git Sha - e4f4dfc

Copy link
Contributor

@slavapestov slavapestov left a 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))
Copy link
Contributor

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?

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>>
Copy link
Contributor

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:)", ())
Copy link
Contributor

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 :) )

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test OS X Platform
Git Sha - f580778

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test Linux Platform
Git Sha - f580778

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test OS X Platform
Git Sha - 6cc3d37

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test Linux Platform
Git Sha - 6cc3d37

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test OS X Platform
Git Sha - 6cc3d37

@aschwaighofer
Copy link
Contributor Author

Please test with following PR:
apple/swift-lldb#1056

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test Linux Platform
Git Sha - 6cc3d37

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test OS X Platform
Git Sha - 6cc3d37

@swift-ci
Copy link
Contributor

swift-ci commented Nov 9, 2018

Build failed
Swift Test Linux Platform
Git Sha - 6cc3d37

@aschwaighofer aschwaighofer merged commit c83f638 into swiftlang:master Nov 9, 2018
@@ -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))
Copy link
Contributor

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.

Copy link
Contributor Author

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).

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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),
Copy link
Contributor

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?

Copy link
Contributor Author

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).

Copy link
Contributor

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(
Copy link
Contributor

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.

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. Thanks. Will fix.


Base().foo()
// This should not be ambigious.
Base().bar()
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants