Skip to content

Commit a3a074c

Browse files
committed
[NFC] [PrintAsClang] Add tiebreaking rule to sort
Because the underlying API for fetching top-level decls returns them in an unspecified order, PrintAsClang sorts the decls before printing them to make the output order more stable. However, the rules currently implemented have at least one known defect (they compare only the unqualified name of a nested class, so two nested classes with the same Swift name sort in an arbitrary order), and there are likely many more. Add a fallback rule which sorts declarations by their mangled name; this should at least distinguish all non-colliding ValueDecls from each other, albeit according to fairly opaque criteria. Additionally add a rule to help distinguish extensions with very similar content, and tweak other logic so that the comparison function is less likely to give up early rather than continuing to look for a usable difference. Fixes rdar://129485103.
1 parent 296af88 commit a3a074c

File tree

3 files changed

+119
-20
lines changed

3 files changed

+119
-20
lines changed

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -680,20 +680,21 @@ class ModuleWriter {
680680
// Sort top level functions with the same C++ name by their location to
681681
// have stable sorting that depends on users source but not on the
682682
// compiler invocation.
683+
// FIXME: This is pretty suspect; PrintAsClang sometimes operates on
684+
// serialized modules which don't have SourceLocs, so this sort
685+
// rule may be applied in some steps of a build but not others.
683686
if ((*rhs)->getLoc().isValid() && (*lhs)->getLoc().isValid()) {
684-
std::string rhsLoc, lhsLoc;
685-
auto getLocText = [](const AbstractFunctionDecl *afd) {
687+
auto getLocText = [](const Decl *afd) {
686688
std::string res;
687689
llvm::raw_string_ostream os(res);
688690
afd->getLoc().print(os, afd->getASTContext().SourceMgr);
689691
return std::move(os.str());
690692
};
691-
if (getLocText(cast<AbstractFunctionDecl>(*lhs)) <
692-
getLocText(cast<AbstractFunctionDecl>(*rhs)))
693-
return Descending;
694-
return Ascending;
693+
694+
result = getLocText(*rhs).compare(getLocText(*lhs));
695+
if (result != 0)
696+
return result;
695697
}
696-
return result;
697698
}
698699

699700
// A function and a global variable can have the same name in C++,
@@ -705,12 +706,35 @@ class ModuleWriter {
705706
return -1;
706707

707708
// Prefer value decls to extensions.
708-
assert(!(isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs)));
709709
if (isa<ValueDecl>(*lhs) && !isa<ValueDecl>(*rhs))
710710
return Descending;
711711
if (!isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs))
712712
return Ascending;
713713

714+
// Last-ditch ValueDecl tiebreaker: Compare mangled names. This captures
715+
// *tons* of context and detail missed by the previous checks, but the
716+
// resulting sort makes little sense to humans.
717+
// FIXME: It'd be nice to share the mangler or even memoize mangled names,
718+
// but we'd have to stop using `llvm::array_pod_sort()` so that we
719+
// could capture some outside state.
720+
Mangle::ASTMangler mangler;
721+
auto getMangledName = [&](const Decl *D) {
722+
auto VD = dyn_cast<ValueDecl>(D);
723+
if (!VD && isa<ExtensionDecl>(D))
724+
VD = cast<ExtensionDecl>(D)->getExtendedNominal();
725+
if (!VD)
726+
return std::string();
727+
return mangler.mangleAnyDecl(VD, /*prefix=*/true,
728+
/*respectOriginallyDefinedIn=*/true);
729+
};
730+
result = getMangledName(*rhs).compare(getMangledName(*lhs));
731+
if (result != 0)
732+
return result;
733+
734+
// Mangled names ought to distinguish all value decls, leaving only
735+
// extensions of the same nominal type beyond this point.
736+
assert(isa<ExtensionDecl>(*lhs) && isa<ExtensionDecl>(*rhs));
737+
714738
// Break ties in extensions by putting smaller extensions last (in reverse
715739
// order).
716740
// FIXME: This will end up taking linear time.
@@ -731,16 +755,48 @@ class ModuleWriter {
731755

732756
// If that fails, arbitrarily pick the extension whose protocols are
733757
// alphabetically first.
734-
auto mismatch =
735-
std::mismatch(lhsProtos.begin(), lhsProtos.end(), rhsProtos.begin(),
736-
[] (const ProtocolDecl *nextLHSProto,
737-
const ProtocolDecl *nextRHSProto) {
738-
return nextLHSProto->getName() != nextRHSProto->getName();
739-
});
740-
if (mismatch.first == lhsProtos.end())
741-
return Equivalent;
742-
StringRef lhsProtoName = (*mismatch.first)->getName().str();
743-
return lhsProtoName.compare((*mismatch.second)->getName().str());
758+
{
759+
auto mismatch =
760+
std::mismatch(lhsProtos.begin(), lhsProtos.end(), rhsProtos.begin(),
761+
[] (const ProtocolDecl *nextLHSProto,
762+
const ProtocolDecl *nextRHSProto) {
763+
return nextLHSProto->getName() != nextRHSProto->getName();
764+
});
765+
if (mismatch.first != lhsProtos.end()) {
766+
StringRef lhsProtoName = (*mismatch.first)->getName().str();
767+
return lhsProtoName.compare((*mismatch.second)->getName().str());
768+
}
769+
}
770+
771+
// Still nothing? Fine, we'll pick the one with the alphabetically first
772+
// member instead.
773+
{
774+
auto mismatch =
775+
std::mismatch(cast<ExtensionDecl>(*lhs)->getMembers().begin(),
776+
cast<ExtensionDecl>(*lhs)->getMembers().end(),
777+
cast<ExtensionDecl>(*rhs)->getMembers().begin(),
778+
[] (const Decl *nextLHSDecl, const Decl *nextRHSDecl) {
779+
if (isa<ValueDecl>(nextLHSDecl) && isa<ValueDecl>(nextRHSDecl)) {
780+
return cast<ValueDecl>(nextLHSDecl)->getName() !=
781+
cast<ValueDecl>(nextRHSDecl)->getName();
782+
}
783+
return isa<ValueDecl>(nextLHSDecl) != isa<ValueDecl>(nextRHSDecl);
784+
});
785+
if (mismatch.first != cast<ExtensionDecl>(*lhs)->getMembers().end()) {
786+
auto *lhsMember = dyn_cast<ValueDecl>(*mismatch.first),
787+
*rhsMember = dyn_cast<ValueDecl>(*mismatch.second);
788+
if (!rhsMember && lhsMember)
789+
return Descending;
790+
if (lhsMember && !rhsMember)
791+
return Ascending;
792+
if (lhsMember && rhsMember)
793+
return rhsMember->getName().compare(lhsMember->getName());
794+
}
795+
}
796+
797+
// Hopefully two extensions with identical conformances and member names
798+
// will be interchangeable enough not to matter.
799+
return Equivalent;
744800
});
745801

746802
assert(declsToWrite.empty());

test/PrintAsObjC/classes.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t) -emit-module -I %S/Inputs/custom-modules -o %t %s -disable-objc-attr-requires-foundation-module
1616
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t) -parse-as-library %t/classes.swiftmodule -typecheck -I %S/Inputs/custom-modules -emit-objc-header-path %t/classes.h -import-objc-header %S/../Inputs/empty.h -disable-objc-attr-requires-foundation-module
17-
// RUN: %FileCheck %s < %t/classes.h
18-
// RUN: %FileCheck --check-prefix=NEGATIVE %s < %t/classes.h
17+
// RUN: %FileCheck %s --input-file %t/classes.h
18+
// RUN: %FileCheck --check-prefix=NEGATIVE %s --input-file %t/classes.h
1919
// RUN: %check-in-clang -I %S/Inputs/custom-modules/ %t/classes.h
2020
// RUN: not %check-in-clang -I %S/Inputs/custom-modules/ -fno-modules -Qunused-arguments %t/classes.h
2121
// RUN: %check-in-clang -I %S/Inputs/custom-modules/ -fno-modules -Qunused-arguments %t/classes.h -include CoreFoundation.h -include objc_generics.h -include SingleGenericClass.h -include CompatibilityAlias.h
@@ -427,6 +427,28 @@ class MyObject : NSObject {}
427427
class ImplicitObjCInner : A1 {}
428428
}
429429

430+
// CHECK-LABEL: @interface NestedCollision1Identical
431+
// CHECK-NEXT: - (void)before
432+
// CHECK-NEXT: init
433+
// CHECK-NEXT: @end
434+
// CHECK: @interface NestedCollision2Identical
435+
// CHECK-NEXT: - (void)after
436+
// CHECK-NEXT: init
437+
// CHECK-NEXT: @end
438+
439+
// We're intentionally declaring NestedCollision2 before NestedCollision1 to
440+
// make sure they're being sorted based on their names, not their source order.
441+
@objc @objcMembers class NestedCollision2 {
442+
@objc(NestedCollision2Identical) @objcMembers class Identical: NSObject {
443+
@objc func after() {}
444+
}
445+
}
446+
@objc @objcMembers class NestedCollision1 {
447+
@objc(NestedCollision1Identical) @objcMembers class Identical: NSObject {
448+
@objc func before() {}
449+
}
450+
}
451+
430452
// CHECK-LABEL: @class Inner2;
431453
// CHECK-LABEL: @interface NestedMembers
432454
// CHECK-NEXT: @property (nonatomic, strong) Inner2 * _Nullable ref2;

test/PrintAsObjC/extensions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ extension A5 {
7676
var notObjC: NotObjC { return NotObjC() }
7777
}
7878

79+
// Check that two otherwise tied extensions will print in alphabetical
80+
// order by first member with a differing Swift name.
81+
82+
// CHECK-LABEL: @interface A6
83+
@objc class A6 {}
84+
85+
extension A6 {
86+
@objc(skippedBool:) func skipped(_: Bool) {}
87+
@objc func def() {}
88+
}
89+
extension A6 {
90+
@objc(skippedInt:) func skipped(_: Int) {}
91+
@objc func abc() {}
92+
}
93+
// CHECK: @interface A6 (SWIFT_EXTENSION(extensions))
94+
// CHECK: - (void)skippedInt:
95+
// CHECK: - (void)abc
96+
// CHECK: @interface A6 (SWIFT_EXTENSION(extensions))
97+
// CHECK: - (void)skippedBool:
98+
// CHECK: - (void)def
99+
79100
// CHECK-LABEL: @interface CustomName{{$}}
80101
// CHECK-NEXT: init
81102
// CHECK-NEXT: @end

0 commit comments

Comments
 (0)