Skip to content

Commit 6c74cce

Browse files
authored
Merge pull request #74643 from beccadax/order-to-chaos-6.0
2 parents c5e8601 + a3a074c commit 6c74cce

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)