Skip to content

Commit c3b57f2

Browse files
authored
Merge pull request #74597 from beccadax/order-to-chaos
[NFC] [PrintAsClang] Add tiebreaking rule to sort
2 parents a8713ad + bf92156 commit c3b57f2

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
@@ -691,20 +691,21 @@ class ModuleWriter {
691691
// Sort top level functions with the same C++ name by their location to
692692
// have stable sorting that depends on users source but not on the
693693
// compiler invocation.
694+
// FIXME: This is pretty suspect; PrintAsClang sometimes operates on
695+
// serialized modules which don't have SourceLocs, so this sort
696+
// rule may be applied in some steps of a build but not others.
694697
if ((*rhs)->getLoc().isValid() && (*lhs)->getLoc().isValid()) {
695-
std::string rhsLoc, lhsLoc;
696-
auto getLocText = [](const AbstractFunctionDecl *afd) {
698+
auto getLocText = [](const Decl *afd) {
697699
std::string res;
698700
llvm::raw_string_ostream os(res);
699701
afd->getLoc().print(os, afd->getASTContext().SourceMgr);
700702
return std::move(os.str());
701703
};
702-
if (getLocText(cast<AbstractFunctionDecl>(*lhs)) <
703-
getLocText(cast<AbstractFunctionDecl>(*rhs)))
704-
return Descending;
705-
return Ascending;
704+
705+
result = getLocText(*rhs).compare(getLocText(*lhs));
706+
if (result != 0)
707+
return result;
706708
}
707-
return result;
708709
}
709710

710711
// A function and a global variable can have the same name in C++,
@@ -716,12 +717,35 @@ class ModuleWriter {
716717
return Ascending;
717718

718719
// Prefer value decls to extensions.
719-
assert(!(isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs)));
720720
if (isa<ValueDecl>(*lhs) && !isa<ValueDecl>(*rhs))
721721
return Descending;
722722
if (!isa<ValueDecl>(*lhs) && isa<ValueDecl>(*rhs))
723723
return Ascending;
724724

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

743767
// If that fails, arbitrarily pick the extension whose protocols are
744768
// alphabetically first.
745-
auto mismatch =
746-
std::mismatch(lhsProtos.begin(), lhsProtos.end(), rhsProtos.begin(),
747-
[] (const ProtocolDecl *nextLHSProto,
748-
const ProtocolDecl *nextRHSProto) {
749-
return nextLHSProto->getName() != nextRHSProto->getName();
750-
});
751-
if (mismatch.first == lhsProtos.end())
752-
return Equivalent;
753-
StringRef lhsProtoName = (*mismatch.first)->getName().str();
754-
return lhsProtoName.compare((*mismatch.second)->getName().str());
769+
{
770+
auto mismatch =
771+
std::mismatch(lhsProtos.begin(), lhsProtos.end(), rhsProtos.begin(),
772+
[] (const ProtocolDecl *nextLHSProto,
773+
const ProtocolDecl *nextRHSProto) {
774+
return nextLHSProto->getName() != nextRHSProto->getName();
775+
});
776+
if (mismatch.first != lhsProtos.end()) {
777+
StringRef lhsProtoName = (*mismatch.first)->getName().str();
778+
return lhsProtoName.compare((*mismatch.second)->getName().str());
779+
}
780+
}
781+
782+
// Still nothing? Fine, we'll pick the one with the alphabetically first
783+
// member instead.
784+
{
785+
auto mismatch =
786+
std::mismatch(cast<ExtensionDecl>(*lhs)->getMembers().begin(),
787+
cast<ExtensionDecl>(*lhs)->getMembers().end(),
788+
cast<ExtensionDecl>(*rhs)->getMembers().begin(),
789+
[] (const Decl *nextLHSDecl, const Decl *nextRHSDecl) {
790+
if (isa<ValueDecl>(nextLHSDecl) && isa<ValueDecl>(nextRHSDecl)) {
791+
return cast<ValueDecl>(nextLHSDecl)->getName() !=
792+
cast<ValueDecl>(nextRHSDecl)->getName();
793+
}
794+
return isa<ValueDecl>(nextLHSDecl) != isa<ValueDecl>(nextRHSDecl);
795+
});
796+
if (mismatch.first != cast<ExtensionDecl>(*lhs)->getMembers().end()) {
797+
auto *lhsMember = dyn_cast<ValueDecl>(*mismatch.first),
798+
*rhsMember = dyn_cast<ValueDecl>(*mismatch.second);
799+
if (!rhsMember && lhsMember)
800+
return Descending;
801+
if (lhsMember && !rhsMember)
802+
return Ascending;
803+
if (lhsMember && rhsMember)
804+
return rhsMember->getName().compare(lhsMember->getName());
805+
}
806+
}
807+
808+
// Hopefully two extensions with identical conformances and member names
809+
// will be interchangeable enough not to matter.
810+
return Equivalent;
755811
});
756812

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