Skip to content

[5.1] Warn when using '@_implementationOnly' inconsistently in a module #24828

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,12 @@ ERROR(assoc_conformance_from_implementation_only_module,none,
"%4); %2 has been imported as implementation-only",
(Type, DeclName, Identifier, Type, Type))

WARNING(warn_implementation_only_conflict,none,
"%0 inconsistently imported as implementation-only",
(Identifier))
NOTE(implementation_only_conflict_here,none,
"imported as implementation-only here", ())

// Derived conformances
ERROR(cannot_synthesize_init_in_extension_of_nonfinal,none,
"implementation of %0 for non-final class cannot be automatically "
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Subsystems.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ namespace swift {
/// emitted.
void performWholeModuleTypeChecking(SourceFile &SF);

/// Checks to see if any of the imports in \p M use `@_implementationOnly` in
/// one file and not in another.
///
/// Like redeclaration checking, but for imports. This isn't part of
/// swift::performWholeModuleTypeChecking because it's linear in the number
/// of declarations in the module.
void checkInconsistentImplementationOnlyImports(ModuleDecl *M);

/// Incrementally type-check only added external definitions.
void typeCheckExternalDefinitions(SourceFile &SF);

Expand Down
2 changes: 2 additions & 0 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,8 @@ void CompilerInstance::finishTypeChecking(
performWholeModuleTypeChecking(SF);
});
}

checkInconsistentImplementationOnlyImports(MainModule);
}

SourceFile *CompilerInstance::createSourceFileForMainModule(
Expand Down
75 changes: 75 additions & 0 deletions lib/Sema/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,81 @@ void swift::performWholeModuleTypeChecking(SourceFile &SF) {
#endif
}

void swift::checkInconsistentImplementationOnlyImports(ModuleDecl *MainModule) {
bool hasAnyImplementationOnlyImports =
llvm::any_of(MainModule->getFiles(), [](const FileUnit *F) -> bool {
auto *SF = dyn_cast<SourceFile>(F);
return SF && SF->hasImplementationOnlyImports();
});
if (!hasAnyImplementationOnlyImports)
return;

auto diagnose = [MainModule](const ImportDecl *normalImport,
const ImportDecl *implementationOnlyImport) {
auto &diags = MainModule->getDiags();
{
InFlightDiagnostic warning =
diags.diagnose(normalImport, diag::warn_implementation_only_conflict,
normalImport->getModule()->getName());
if (normalImport->getAttrs().isEmpty()) {
// Only try to add a fix-it if there's no other annotations on the
// import to avoid creating things like
// `@_implementationOnly @_exported import Foo`. The developer can
// resolve those manually.
warning.fixItInsert(normalImport->getStartLoc(),
"@_implementationOnly ");
}
}
diags.diagnose(implementationOnlyImport,
diag::implementation_only_conflict_here);
};

llvm::DenseMap<ModuleDecl *, std::vector<const ImportDecl *>> normalImports;
llvm::DenseMap<ModuleDecl *, const ImportDecl *> implementationOnlyImports;

for (const FileUnit *file : MainModule->getFiles()) {
auto *SF = dyn_cast<SourceFile>(file);
if (!SF)
continue;

for (auto *topLevelDecl : SF->Decls) {
auto *nextImport = dyn_cast<ImportDecl>(topLevelDecl);
if (!nextImport)
continue;

ModuleDecl *module = nextImport->getModule();
if (nextImport->getAttrs().hasAttribute<ImplementationOnlyAttr>()) {
// We saw an implementation-only import.
bool isNew =
implementationOnlyImports.insert({module, nextImport}).second;
if (!isNew)
continue;

auto seenNormalImportPosition = normalImports.find(module);
if (seenNormalImportPosition != normalImports.end()) {
for (auto *seenNormalImport : seenNormalImportPosition->getSecond())
diagnose(seenNormalImport, nextImport);

// We're done with these; keep the map small if possible.
normalImports.erase(seenNormalImportPosition);
}
continue;
}

// We saw a non-implementation-only import. Is that in conflict with what
// we've seen?
if (auto *seenImplementationOnlyImport =
implementationOnlyImports.lookup(module)) {
diagnose(nextImport, seenImplementationOnlyImport);
continue;
}

// Otherwise, record it for later.
normalImports[module].push_back(nextImport);
}
}
}

bool swift::performTypeLocChecking(ASTContext &Ctx, TypeLoc &T,
DeclContext *DC,
bool ProduceDiagnostics) {
Expand Down
4 changes: 2 additions & 2 deletions test/ParseableInterface/Inputs/imports-other.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import A
import B.B3
import D

import NotSoSecret
@_implementationOnly import NotSoSecret2
import NotSoSecret // expected-warning {{'NotSoSecret' inconsistently imported as implementation-only}}
@_implementationOnly import NotSoSecret2 // expected-note {{imported as implementation-only here}}
4 changes: 2 additions & 2 deletions test/ParseableInterface/imports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import func C.c // expected-warning {{scoped imports are not yet supported in mo
import D
@_implementationOnly import Secret_BAD

@_implementationOnly import NotSoSecret
import NotSoSecret2
@_implementationOnly import NotSoSecret // expected-note {{imported as implementation-only here}}
import NotSoSecret2 // expected-warning {{'NotSoSecret2' inconsistently imported as implementation-only}}

// CHECK-NOT: import
// CHECK: {{^}}import A{{$}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import NotSoSecret // expected-warning {{'NotSoSecret' inconsistently imported as implementation-only}}
@_implementationOnly import NotSoSecret2 // expected-note 2 {{imported as implementation-only here}}
import NotSoSecret3 // expected-warning {{'NotSoSecret3' inconsistently imported as implementation-only}}

@_implementationOnly import ActuallySecret // no-warning
import ActuallyOkay // no-warning

@_implementationOnly import Contradictory // expected-note {{imported as implementation-only here}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@_implementationOnly import NotSoSecret
import NotSoSecret2 // expected-warning {{'NotSoSecret2' inconsistently imported as implementation-only}}
@_implementationOnly import NotSoSecret3 // expected-note 2 {{imported as implementation-only here}}

@_implementationOnly import ActuallySecret // no-warning
import ActuallyOkay // no-warning
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module NotSoSecret {}
module NotSoSecret2 {}
module NotSoSecret3 {}

module ActuallySecret {}
module ActuallyOkay {}

module Contradictory {}
15 changes: 15 additions & 0 deletions test/decl/import/inconsistent-implementation-only.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Check that the diagnostics are produced regardless of what primary file we're using.
// RUN: %target-swift-frontend -typecheck -primary-file %s %S/Inputs/inconsistent-implementation-only/2.swift %S/Inputs/inconsistent-implementation-only/3.swift -I %S/Inputs/inconsistent-implementation-only/ -verify
// RUN: %target-swift-frontend -typecheck %s -primary-file %S/Inputs/inconsistent-implementation-only/2.swift %S/Inputs/inconsistent-implementation-only/3.swift -I %S/Inputs/inconsistent-implementation-only/ -verify
// RUN: %target-swift-frontend -typecheck %s %S/Inputs/inconsistent-implementation-only/2.swift -primary-file %S/Inputs/inconsistent-implementation-only/3.swift -I %S/Inputs/inconsistent-implementation-only/ -verify
// RUN: %target-swift-frontend -typecheck -primary-file %s -primary-file %S/Inputs/inconsistent-implementation-only/2.swift -primary-file %S/Inputs/inconsistent-implementation-only/3.swift -I %S/Inputs/inconsistent-implementation-only/ -verify
// RUN: %target-swift-frontend -typecheck %s %S/Inputs/inconsistent-implementation-only/2.swift %S/Inputs/inconsistent-implementation-only/3.swift -I %S/Inputs/inconsistent-implementation-only/ -verify

@_implementationOnly import NotSoSecret // expected-note {{imported as implementation-only here}}
import NotSoSecret2 // expected-warning {{'NotSoSecret2' inconsistently imported as implementation-only}} {{1-1=@_implementationOnly }}
import NotSoSecret3 // expected-warning {{'NotSoSecret3' inconsistently imported as implementation-only}} {{1-1=@_implementationOnly }}

@_implementationOnly import ActuallySecret // no-warning
import ActuallyOkay // no-warning

@_exported import Contradictory // expected-warning {{'Contradictory' inconsistently imported as implementation-only}} {{none}}