Skip to content

Commit da07ff5

Browse files
committed
[PrintAsClang] Warn about unstable decl order
PrintAsClang is supposed to emit declarations in the same order regardless of the compiler’s internal state, but we have repeatedly found that our current criteria are inadequate, resulting in non-functionality-affecting changes to generated header content. Add a diagnostic that’s emitted when this happens soliciting a bug report. Since there *should* be no cases where the compiler fails to order declarations, this diagnostic is never actually emitted. Instead, we test this change by enabling `-verify` on nearly all PrintAsClang tests to make sure they are unaffected. This did demonstrate a missing criterion that only mattered in C++ mode: extensions that varied only in their generic signature were not sorted stably. Add a sort criterion for this.
1 parent d2100b7 commit da07ff5

File tree

222 files changed

+353
-292
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

222 files changed

+353
-292
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6345,6 +6345,17 @@ ERROR(invalid_objc_swift_rooted_class,none,
63456345
NOTE(invalid_objc_swift_root_class_insert_nsobject,none,
63466346
"inherit from 'NSObject' to silence this error", ())
63476347

6348+
WARNING(objc_header_sorting_arbitrary,none,
6349+
"arbitrarily printing %0 before %1 in generated header; subsequent "
6350+
"compilations may print them in a different order",
6351+
(Decl *, Decl *))
6352+
NOTE(objc_header_sorting_arbitrary_other,none,
6353+
"%1 will be printed after %0 during this compilation",
6354+
(Decl *, Decl *))
6355+
NOTE(objc_header_sorting_arbitrary_please_report,none,
6356+
"please report this as a bug in the Swift compiler and describe both "
6357+
"declarations", ())
6358+
63486359
ERROR(invalid_nonobjc_decl,none,
63496360
"only class members and extensions of classes can be declared @nonobjc", ())
63506361
ERROR(invalid_nonobjc_extension,none,

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -751,21 +751,45 @@ class ModuleWriter {
751751
if (result != 0)
752752
return result;
753753

754+
auto lastDitchSort = [&](bool suppressDiagnostic) -> int {
755+
// With no other criteria, we'll sort by memory address.
756+
if (*lhs < *rhs)
757+
result = Ascending;
758+
else if (*lhs > *rhs)
759+
result = Descending;
760+
else {
761+
// Sorting with yourself shouldn't happen (but implement consistent
762+
// behavior if this assert is disabled).
763+
ASSERT(*lhs != *rhs && "sorting should not compare decl to itself");
764+
result = Equivalent;
765+
}
766+
767+
// Warn that this isn't stable across different compilations.
768+
if (!suppressDiagnostic) {
769+
auto earlier = (result == Ascending) ? *lhs : *rhs;
770+
auto later = (result == Ascending) ? *rhs : *lhs;
771+
772+
earlier->diagnose(diag::objc_header_sorting_arbitrary,
773+
earlier, later);
774+
later->diagnose(diag::objc_header_sorting_arbitrary_other,
775+
earlier, later);
776+
earlier->diagnose(diag::objc_header_sorting_arbitrary_please_report);
777+
}
778+
779+
return result;
780+
};
781+
754782
// Mangled names ought to distinguish all value decls, leaving only
755783
// extensions of the same nominal type beyond this point.
756-
assert(isa<ExtensionDecl>(*lhs) && isa<ExtensionDecl>(*rhs));
784+
if (!isa<ExtensionDecl>(*lhs) || !isa<ExtensionDecl>(*rhs))
785+
return lastDitchSort(/*suppressDiagnostic=*/false);
757786

758787
// Break ties in extensions by putting smaller extensions last (in reverse
759788
// order).
760-
// FIXME: This will end up taking linear time.
761789
auto lhsMembers = cast<ExtensionDecl>(*lhs)->getAllMembers();
762790
auto rhsMembers = cast<ExtensionDecl>(*rhs)->getAllMembers();
763-
unsigned numLHSMembers = std::distance(lhsMembers.begin(),
764-
lhsMembers.end());
765-
unsigned numRHSMembers = std::distance(rhsMembers.begin(),
766-
rhsMembers.end());
767-
if (numLHSMembers != numRHSMembers)
768-
return numLHSMembers < numRHSMembers ? Descending : Ascending;
791+
if (lhsMembers.size() != rhsMembers.size())
792+
return lhsMembers.size() < rhsMembers.size() ? Descending : Ascending;
769793

770794
// Or the extension with fewer protocols.
771795
auto lhsProtos = cast<ExtensionDecl>(*lhs)->getLocalProtocols();
@@ -784,7 +808,9 @@ class ModuleWriter {
784808
});
785809
if (mismatch.first != lhsProtos.end()) {
786810
StringRef lhsProtoName = (*mismatch.first)->getName().str();
787-
return lhsProtoName.compare((*mismatch.second)->getName().str());
811+
result = lhsProtoName.compare((*mismatch.second)->getName().str());
812+
if (result != 0)
813+
return result;
788814
}
789815
}
790816

@@ -820,9 +846,23 @@ class ModuleWriter {
820846
}
821847
}
822848

823-
// Hopefully two extensions with identical conformances and member names
824-
// will be interchangeable enough not to matter.
825-
return Equivalent;
849+
// Tough customer. Maybe they have different generic signatures?
850+
{
851+
auto lhsSig = cast<ExtensionDecl>(*lhs)->getGenericSignature()
852+
.getAsString();
853+
auto rhsSig = cast<ExtensionDecl>(*rhs)->getGenericSignature()
854+
.getAsString();
855+
result = rhsSig.compare(lhsSig);
856+
if (result != 0)
857+
return result;
858+
}
859+
860+
// Nothing, sadly.
861+
bool areEmptyExtensions = lhsMembers.size() == 0
862+
&& rhsMembers.size() == 0
863+
&& lhsProtos.size() == 0
864+
&& rhsProtos.size() == 0;
865+
return lastDitchSort(/*suppressDiagnostic=*/areEmptyExtensions);
826866
});
827867

828868
assert(declsToWrite.empty());

test/Interop/CxxToSwiftToCxx/allow-shared-frt-back-to-cxx.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
4+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
55

66
// RUN: %FileCheck %s < %t/UseCxxTy.h
77

test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
4+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
55

66
// RUN: %FileCheck %s --input-file %t/UseCxxTy.h
77

8-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTyExposeOnly.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=has-expose-attr -disable-availability-checking
8+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTyExposeOnly.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=has-expose-attr -disable-availability-checking
99

1010
// RUN: %FileCheck %s --input-file %t/UseCxxTyExposeOnly.h
1111

test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-objcxx.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %S/bridge-cxx-struct-back-to-cxx.swift %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
4+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
55
// RUN: echo "#include \"header.h\"" > %t/full-cxx-swift-cxx-bridging.h
66
// RUN: cat %t/UseCxxTy.h >> %t/full-cxx-swift-cxx-bridging.h
77

test/Interop/CxxToSwiftToCxx/consuming-cxx-struct-parameter-back-to-cxx-execution.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxx -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
4+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxx -typecheck -verify -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking
55

66
// RUN: %target-interop-build-clangxx -std=c++20 -c %t/use-swift-cxx-types.cpp -I %t -o %t/swift-cxx-execution.o
77
// RUN: %target-interop-build-swift %t/use-cxx-types.swift -o %t/swift-cxx-execution -Xlinker %t/swift-cxx-execution.o -module-name UseCxx -Xfrontend -entry-point-function-name -Xfrontend swiftMain -I %t -O -Xfrontend -disable-availability-checking

test/Interop/CxxToSwiftToCxx/link-cxx-type-metadata-accessor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxx -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public
4+
// RUN: %target-swift-frontend %t/use-cxx-types.swift -module-name UseCxx -typecheck -verify -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public
55

66
// Use the 'DEBUG' flug to force llvm used
77
// RUN: %target-interop-build-clangxx -std=c++20 -c %t/use-swift-cxx-types.cpp -I %t -o %t/swift-cxx-execution.o -DDEBUG

test/Interop/CxxToSwiftToCxx/simd-bridge-cxx-struct-back-to-cxx.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -disable-availability-checking -Xcc -DSIMD_NO_CODE
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %t/use-cxx-types.swift -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -disable-availability-checking -Xcc -DSIMD_NO_CODE
55

66
// RUN: %FileCheck %s < %t/UseCxxTy.h
77

test/Interop/CxxToSwiftToCxx/span/span-execution.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: split-file %s %t
33

4-
// RUN: %target-swift-frontend -typecheck %t/use-span.swift -typecheck -module-name UseSpan -emit-clang-header-path %t/UseSpan.h -I %t -enable-experimental-cxx-interop -Xcc -Xclang -Xcc -fmodule-format=raw -Xcc -std=c++20 -clang-header-expose-decls=all-public
4+
// RUN: %target-swift-frontend %t/use-span.swift -module-name UseSpan -typecheck -verify -emit-clang-header-path %t/UseSpan.h -I %t -enable-experimental-cxx-interop -Xcc -Xclang -Xcc -fmodule-format=raw -Xcc -std=c++20 -clang-header-expose-decls=all-public
55

66
// RUN: %target-interop-build-clangxx -std=c++20 -c %t/use-span.cpp -I %t -o %t/swift-cxx-execution.o
77
// RUN: %target-interop-build-swift %t/use-span.swift -o %t/swift-cxx-execution -Xlinker %t/swift-cxx-execution.o -module-name UseSpan -Xfrontend -entry-point-function-name -Xfrontend swiftMain -I %t -O -Xcc --std=c++20

test/Interop/CxxToSwiftToObjcxx/bridge-cxx-types-to-objcxx.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %empty-directory(%t)
22

3-
// RUN: %target-swift-frontend -typecheck %s -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -I %S/Inputs -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -target arm64-apple-macosx13.3
3+
// RUN: %target-swift-frontend %s -module-name UseCxxTy -typecheck -verify -emit-clang-header-path %t/UseCxxTy.h -I %t -I %S/Inputs -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -target arm64-apple-macosx13.3
44
// RUN: %FileCheck %s < %t/UseCxxTy.h
55

66
// REQUIRES: OS=macosx

0 commit comments

Comments
 (0)