Skip to content

[6.2][InterfaceGen] Merge exported modules with the same public name in generated interface #81259

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 1 commit into from
May 8, 2025
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
7 changes: 7 additions & 0 deletions include/swift/AST/PrintOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ struct PrintOptions {
/// \see FileUnit::getExportedModuleName
bool UseExportedModuleNames = false;

/// If true, printed module names will use the "public" (for documentation)
/// name, which may be different from the regular name.
///
/// \see FileUnit::getPublicModuleName
bool UsePublicModuleNames = false;

/// Use the original module name to qualify a symbol.
bool UseOriginallyDefinedInModuleNames = false;

Expand Down Expand Up @@ -704,6 +710,7 @@ struct PrintOptions {
result.MapCrossImportOverlaysToDeclaringModule = true;
result.PrintCurrentMembersOnly = false;
result.SuppressExpandedMacros = true;
result.UsePublicModuleNames = true;
return result;
}

Expand Down
7 changes: 5 additions & 2 deletions include/swift/IDE/ModuleInterfacePrinting.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ namespace ide {
/// Flags used when traversing a module for printing.
enum class ModuleTraversal : unsigned {
/// Visit modules even if their contents wouldn't be visible to name lookup.
VisitHidden = 0x01,
VisitHidden = 0x01,
/// Visit submodules.
VisitSubmodules = 0x02,
/// Skip the declarations in a Swift overlay module.
SkipOverlay = 0x04,
SkipOverlay = 0x04,
/// Visit exported modules where their public module name matches the current
/// module.
VisitMatchingExported = 0x08,
};

/// Options used to describe the traversal of a module for printing.
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6012,6 +6012,11 @@ class TypePrinter : public TypeVisitor<TypePrinter> {
Name = Mod->getASTContext().getIdentifier(ExportedModuleName);
}

StringRef PublicModuleName = File->getPublicModuleName();
if (Options.UsePublicModuleNames && !PublicModuleName.empty()) {
Name = Mod->getASTContext().getIdentifier(PublicModuleName);
}

if (Options.UseOriginallyDefinedInModuleNames) {
Decl *D = Ty->getDecl();
for (auto attr: D->getAttrs().getAttributes<OriginallyDefinedInAttr>()) {
Expand Down
149 changes: 98 additions & 51 deletions lib/IDE/ModuleInterfacePrinting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,34 @@ static bool compareSwiftDecls(Decl *LHS, Decl *RHS) {
return LHS->getKind() < RHS->getKind();
}

static bool shouldPrintImport(ImportDecl *ImportD, ModuleDecl *OrigMod,
const clang::Module *OrigClangMod) {
if (ImportD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return false;

auto *ImportedMod = ImportD->getModule();
if (ImportedMod) {
if (ImportedMod == OrigMod)
return false;
if (ImportedMod->isOnoneSupportModule())
return false;
if (ImportedMod->getName().hasUnderscoredNaming())
return false;
}

if (!OrigClangMod)
return true;

auto ImportedClangMod = ImportD->getClangModule();
if (!ImportedClangMod)
return true;
if (!ImportedClangMod->isSubModule())
return true;
if (ImportedClangMod == OrigClangMod)
return false;
return ImportedClangMod->isSubModuleOf(OrigClangMod);
}

static std::pair<ArrayRef<Decl*>, ArrayRef<Decl*>>
getDeclsFromCrossImportOverlay(ModuleDecl *Overlay, ModuleDecl *Declaring,
SmallVectorImpl<Decl *> &Decls,
Expand All @@ -329,7 +357,8 @@ getDeclsFromCrossImportOverlay(ModuleDecl *Overlay, ModuleDecl *Declaring,

// Ignore imports of the underlying module, or any cross-import
// that would map back to it.
if (Imported == Declaring || Imported->isCrossImportOverlayOf(Declaring))
if (!shouldPrintImport(ID, Declaring, nullptr) ||
Imported->isCrossImportOverlayOf(Declaring))
return false;

// Ignore an imports of modules also imported by the underlying module.
Expand Down Expand Up @@ -457,19 +486,40 @@ void swift::ide::printModuleInterface(
auto AdjustedOptions = Options;
adjustPrintOptions(AdjustedOptions);

llvm::DenseSet<const void *> SeenImportedDecls;
SmallVector<ModuleDecl *, 1> ModuleList;
ModuleList.push_back(TargetMod);
SeenImportedDecls.insert(TargetMod);

SmallVector<ImportDecl *, 1> ImportDecls;
llvm::DenseSet<const clang::Module *> ClangModulesForImports;
SmallVector<Decl *, 1> SwiftDecls;
SmallVector<ImportDecl *, 0> ImportDecls;
SmallVector<Decl *, 0> SwiftDecls;
llvm::DenseMap<const clang::Module *,
SmallVector<std::pair<Decl *, clang::SourceLocation>, 1>>
ClangDecls;
SmallVector<std::pair<Decl *, clang::SourceLocation>, 0>>
ClangDecls;

// Add exported modules that have the same public module name as this module
// (excluding the underlying clang module if there is one).
if (TraversalOptions & ModuleTraversal::VisitMatchingExported) {
SmallVector<ImportedModule> Imports;
TargetMod->getImportedModules(Imports,
ModuleDecl::ImportFilterKind::Exported);
for (ImportedModule Import : Imports) {
if (Import.importedModule->getPublicModuleName(
/*onlyIfImported=*/false) != TargetMod->getName())
continue;

if (TargetClangMod != nullptr &&
Import.importedModule->findUnderlyingClangModule() == TargetClangMod)
continue;

ModuleList.push_back(Import.importedModule);
SeenImportedDecls.insert(Import.importedModule);
}
}

// If we're printing recursively, find all of the submodules to print.
if (TargetClangMod) {
if (TraversalOptions) {
// Add clang submodules if they're being visited
if (TraversalOptions & ModuleTraversal::VisitSubmodules) {
SmallVector<const clang::Module *, 8> Worklist;
SmallPtrSet<const clang::Module *, 8> Visited;
Worklist.push_back(TargetClangMod);
Expand All @@ -482,16 +532,15 @@ void swift::ide::printModuleInterface(

ClangDecls.insert({ CM, {} });

if (CM != TargetClangMod)
if (auto *OwningModule = Importer.getWrapperForModule(CM))
if (CM != TargetClangMod) {
if (auto *OwningModule = Importer.getWrapperForModule(CM)) {
ModuleList.push_back(OwningModule);
}
}

// If we're supposed to visit submodules, add them now.
if (TraversalOptions & ModuleTraversal::VisitSubmodules) {
for (clang::Module * submodule: CM->submodules()) {
if (Visited.insert(submodule).second) {
Worklist.push_back(submodule);
}
for (clang::Module *submodule : CM->submodules()) {
if (Visited.insert(submodule).second) {
Worklist.push_back(submodule);
}
}
}
Expand All @@ -500,8 +549,7 @@ void swift::ide::printModuleInterface(
}
}

SmallVector<Decl *, 1> Decls;

SmallVector<Decl *, 0> Decls;
for (ModuleDecl *M : ModuleList) {
swift::getTopLevelDeclsForDisplay(M, Decls);
}
Expand All @@ -527,42 +575,38 @@ void swift::ide::printModuleInterface(
continue;
}

auto ShouldPrintImport = [&](ImportDecl *ImportD) -> bool {
if (ImportD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return false;
if (auto ID = dyn_cast<ImportDecl>(D)) {
if (!shouldPrintImport(ID, TargetMod, TargetClangMod))
continue;

if (!TargetClangMod)
return true;
if (ImportD->getModule() == TargetMod)
return false;
// Erase submodules that are not missing
if (ID->getClangModule())
NoImportSubModules.erase(ID->getClangModule());

auto ImportedMod = ImportD->getClangModule();
if (!ImportedMod)
return true;
if (!ImportedMod->isSubModule())
return true;
if (ImportedMod == TargetClangMod)
return false;
return ImportedMod->isSubModuleOf(TargetClangMod);
};
if (ID->getImportKind() == ImportKind::Module) {
// Could have a duplicate import from a clang module's overlay or
// because we're merging modules. Skip them.

if (auto ID = dyn_cast<ImportDecl>(D)) {
if (ShouldPrintImport(ID)) {
if (ID->getClangModule())
// Erase those submodules that are not missing.
NoImportSubModules.erase(ID->getClangModule());
if (ID->getImportKind() == ImportKind::Module) {
// Make sure we don't print duplicate imports, due to getting imports
// for both a clang module and its overlay.
if (auto *ClangMod = getUnderlyingClangModuleForImport(ID)) {
auto P = ClangModulesForImports.insert(ClangMod);
bool IsNew = P.second;
if (!IsNew)
continue;
}
if (auto *ClangMod = getUnderlyingClangModuleForImport(ID)) {
if (!SeenImportedDecls.insert(ClangMod).second)
continue;
}
ImportDecls.push_back(ID);

if (auto *ImportedMod = ID->getModule()) {
if (!SeenImportedDecls.insert(ImportedMod).second)
continue;
}
} else {
bool AnyNewDecls = false;
for (auto *ImportedDecl : ID->getDecls()) {
AnyNewDecls |= SeenImportedDecls.insert(ImportedDecl).second;
}
if (!AnyNewDecls)
continue;
}

ImportDecls.push_back(ID);

continue;
}

Expand Down Expand Up @@ -684,9 +728,12 @@ void swift::ide::printModuleInterface(

// Imports from the stdlib are internal details that don't need to be exposed.
if (!TargetMod->isStdlibModule()) {
for (auto *D : ImportDecls)
for (auto *D : ImportDecls) {
PrintDecl(D);
Printer.printNewline();
}
if (!ImportDecls.empty()) {
Printer.printNewline();
}
}

{
Expand Down
2 changes: 0 additions & 2 deletions test/IDE/Inputs/foo_swift_module.printed.comments.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import SwiftOnoneSupport

func %%% (lhs: Int, rhs: Int) -> Int
postfix func =-> (lhs: Int) -> Int
postfix func => (lhs: Int) -> Int
Expand Down
2 changes: 0 additions & 2 deletions test/IDE/print_clang_framework_with_overlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,5 @@ import FooOverlay
// CHECK: @_exported import Foo
// CHECK: @_exported import struct Foo.FooStruct1
// CHECK: @_exported import Foo.FooSub
// CHECK: @_exported import func Foo.FooSub.fooSubFunc1
// FIXME: this duplicate import is silly, but not harmful.
// CHECK: @_exported import func Foo.fooSubFunc1
// CHECK: func fooSubOverlayFunc1(x: Int32) -> Int32
12 changes: 6 additions & 6 deletions test/SourceKit/CursorInfo/cursor_generated_interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ASwiftType {
}

// LibA is a mixed framework with no source info and a submodule
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=12:36 -print-raw-response | %FileCheck %s --check-prefix=CHECKA
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=11:36 -print-raw-response | %FileCheck %s --check-prefix=CHECKA
// CHECKA: key.name: "ASwiftType"
// CHECKA: key.modulename: "LibA"
// CHECKA: key.decl_lang: source.lang.swift
Expand All @@ -60,7 +60,7 @@ framework module LibA {
@interface AObjcType
@end

// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=12:54 -print-raw-response | %FileCheck %s --check-prefix=CHECKAOBJ
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=11:54 -print-raw-response | %FileCheck %s --check-prefix=CHECKAOBJ
// CHECKAOBJ: key.name: "AObjcType"
// CHECKAOBJ: key.line: [[@LINE-5]]
// CHECKAOBJ: key.column: 12
Expand All @@ -72,7 +72,7 @@ framework module LibA {
@interface ASubType
@end

// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=12:70 -print-raw-response | %FileCheck %s --check-prefix=CHECKASUB
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=11:70 -print-raw-response | %FileCheck %s --check-prefix=CHECKASUB
// CHECKASUB: key.name: "ASubType"
// CHECKASUB: key.line: [[@LINE-5]]
// CHECKASUB: key.column: 12
Expand All @@ -84,7 +84,7 @@ framework module LibA {
public class BType {}

// LibB has source info
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=14:32 -print-raw-response | %FileCheck %s --check-prefix=CHECKB
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=13:32 -print-raw-response | %FileCheck %s --check-prefix=CHECKB
// CHECKB: key.name: "BType"
// CHECKB: key.line: [[@LINE-5]]
// CHECKB: key.column: 14
Expand All @@ -96,7 +96,7 @@ public class BType {}
public class CType {}

// LibC has no source info
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=14:47 -print-raw-response | %FileCheck %s --check-prefix=CHECKC
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=13:47 -print-raw-response | %FileCheck %s --check-prefix=CHECKC
// CHECKC: key.name: "CType"
// CHECKC: key.modulename: "LibC"
// CHECKC: key.decl_lang: source.lang.swift
Expand All @@ -105,7 +105,7 @@ public class CType {}
@interface DType
@end

// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=14:57 -print-raw-response | %FileCheck %s --check-prefix=CHECKD
// RUN: %sourcekitd-test -req=interface-gen-open -module LibA -- -F %t/frameworks -target %target-triple == -req=cursor -pos=13:57 -print-raw-response | %FileCheck %s --check-prefix=CHECKD
// CHECKD: key.name: "DType"
// CHECKD: key.line: [[@LINE-5]]
// CHECKD: key.column: 12
Expand Down
Loading