Skip to content

Commit 1b5adb7

Browse files
authored
Merge pull request #6480 from kballard/objc-availability-attributes
[PrintAsObjC] Add availability attributes to Obj-C declarations
2 parents 0150938 + 0aa751d commit 1b5adb7

File tree

2 files changed

+302
-0
lines changed

2 files changed

+302
-0
lines changed

lib/PrintAsObjC/PrintAsObjC.cpp

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/Frontend/Frontend.h"
2626
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
2727
#include "swift/IDE/CommentConversion.h"
28+
#include "swift/Parse/Lexer.h"
2829
#include "clang/AST/ASTContext.h"
2930
#include "clang/AST/Attr.h"
3031
#include "clang/AST/Decl.h"
@@ -33,6 +34,7 @@
3334
#include "clang/Basic/Module.h"
3435
#include "clang/Lex/Lexer.h"
3536
#include "llvm/ADT/SetVector.h"
37+
#include "llvm/ADT/SmallString.h"
3638
#include "llvm/ADT/StringSwitch.h"
3739
#include "llvm/ADT/STLExtras.h"
3840
#include "llvm/Support/Path.h"
@@ -285,6 +287,42 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
285287
ide::getDocumentationCommentAsDoxygen(DC.getValue(), os);
286288
}
287289

290+
/// Prints an encoded string, escaped properly for C.
291+
void printEncodedString(StringRef str, bool includeQuotes = true) {
292+
// NB: We don't use raw_ostream::write_escaped() because it does hex escapes
293+
// for non-ASCII chars.
294+
295+
llvm::SmallString<128> Buf;
296+
StringRef decodedStr = Lexer::getEncodedStringSegment(str, Buf);
297+
298+
if (includeQuotes) os << '"';
299+
for (unsigned char c : decodedStr) {
300+
switch (c) {
301+
case '\\':
302+
os << '\\' << '\\';
303+
break;
304+
case '\t':
305+
os << '\\' << 't';
306+
break;
307+
case '\n':
308+
os << '\\' << 'n';
309+
break;
310+
case '"':
311+
os << '\\' << '"';
312+
break;
313+
default:
314+
if (c < 0x20 || c == 0x7F) {
315+
os << '\\' << 'x';
316+
os << llvm::hexdigit((c >> 4) & 0xF);
317+
os << llvm::hexdigit((c >> 0) & 0xF);
318+
} else {
319+
os << c;
320+
}
321+
}
322+
}
323+
if (includeQuotes) os << '"';
324+
}
325+
288326
// Ignore other declarations.
289327
void visitDecl(Decl *D) {}
290328

@@ -569,13 +607,15 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
569607
++paramIndex;
570608
}
571609

610+
bool skipAvailability = false;
572611
// Swift designated initializers are Objective-C designated initializers.
573612
if (auto ctor = dyn_cast<ConstructorDecl>(AFD)) {
574613
if (ctor->hasStubImplementation()
575614
|| ctor->getFormalAccess() < minRequiredAccess) {
576615
// This will only be reached if the overridden initializer has the
577616
// required access
578617
os << " SWIFT_UNAVAILABLE";
618+
skipAvailability = true;
579619
} else if (ctor->isDesignatedInit() &&
580620
!isa<ProtocolDecl>(ctor->getDeclContext())) {
581621
os << " OBJC_DESIGNATED_INITIALIZER";
@@ -594,6 +634,10 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
594634
}
595635
}
596636

637+
if (!skipAvailability) {
638+
appendAvailabilityAttribute(AFD);
639+
}
640+
597641
os << ";\n";
598642
}
599643

@@ -631,9 +675,131 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
631675

632676
// Finish the result type.
633677
multiPart.finish();
678+
679+
appendAvailabilityAttribute(FD);
634680

635681
os << ';';
636682
}
683+
684+
void appendAvailabilityAttribute(const ValueDecl *VD) {
685+
for (auto Attr : VD->getAttrs()) {
686+
if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
687+
if (AvAttr->isInvalid()) continue;
688+
if (AvAttr->Platform == PlatformKind::none) {
689+
if (AvAttr->PlatformAgnostic == PlatformAgnosticAvailabilityKind::Unavailable) {
690+
// Availability for *
691+
if (!AvAttr->Rename.empty()) {
692+
// NB: Don't bother getting obj-c names, we can't get one for the rename
693+
os << " SWIFT_UNAVAILABLE_MSG(\"'" << VD->getName() << "' has been renamed to '";
694+
printEncodedString(AvAttr->Rename, false);
695+
os << '\'';
696+
if (!AvAttr->Message.empty()) {
697+
os << ": ";
698+
printEncodedString(AvAttr->Message, false);
699+
}
700+
os << "\")";
701+
} else if (!AvAttr->Message.empty()) {
702+
os << " SWIFT_UNAVAILABLE_MSG(";
703+
printEncodedString(AvAttr->Message);
704+
os << ")";
705+
} else {
706+
os << " SWIFT_UNAVAILABLE";
707+
}
708+
break;
709+
}
710+
if (AvAttr->isUnconditionallyDeprecated()) {
711+
if (!AvAttr->Rename.empty() || !AvAttr->Message.empty()) {
712+
os << " SWIFT_DEPRECATED_MSG(";
713+
printEncodedString(AvAttr->Message);
714+
if (!AvAttr->Rename.empty()) {
715+
os << ", ";
716+
printEncodedString(AvAttr->Rename);
717+
}
718+
os << ")";
719+
} else {
720+
os << " SWIFT_DEPRECATED";
721+
}
722+
}
723+
continue;
724+
}
725+
// Availability for a specific platform
726+
if (!AvAttr->Introduced.hasValue()
727+
&& !AvAttr->Deprecated.hasValue()
728+
&& !AvAttr->Obsoleted.hasValue()
729+
&& !AvAttr->isUnconditionallyDeprecated()
730+
&& !AvAttr->isUnconditionallyUnavailable()) {
731+
continue;
732+
}
733+
const char *plat = NULL;
734+
switch (AvAttr->Platform) {
735+
case PlatformKind::OSX:
736+
plat = "macos";
737+
break;
738+
case PlatformKind::iOS:
739+
plat = "ios";
740+
break;
741+
case PlatformKind::tvOS:
742+
plat = "tvos";
743+
break;
744+
case PlatformKind::watchOS:
745+
plat = "watchos";
746+
break;
747+
case PlatformKind::OSXApplicationExtension:
748+
plat = "macos_app_extension";
749+
break;
750+
case PlatformKind::iOSApplicationExtension:
751+
plat = "ios_app_extension";
752+
break;
753+
case PlatformKind::tvOSApplicationExtension:
754+
plat = "tvos_app_extension";
755+
break;
756+
case PlatformKind::watchOSApplicationExtension:
757+
plat = "watchos_app_extension";
758+
break;
759+
default:
760+
break;
761+
}
762+
if (plat == NULL) continue;
763+
os << " SWIFT_AVAILABILITY(" << plat;
764+
if (AvAttr->isUnconditionallyUnavailable()) {
765+
os << ",unavailable";
766+
} else {
767+
if (AvAttr->Introduced.hasValue()) {
768+
os << ",introduced=" << AvAttr->Introduced.getValue().getAsString();
769+
}
770+
if (AvAttr->Deprecated.hasValue()) {
771+
os << ",deprecated=" << AvAttr->Deprecated.getValue().getAsString();
772+
} else if (AvAttr->isUnconditionallyDeprecated()) {
773+
// We need to specify some version, we can't just say deprecated.
774+
// We also can't deprecate it before it's introduced.
775+
if (AvAttr->Introduced.hasValue()) {
776+
os << ",deprecated=" << AvAttr->Introduced.getValue().getAsString();
777+
} else {
778+
os << ",deprecated=0.0.1";
779+
}
780+
}
781+
if (AvAttr->Obsoleted.hasValue()) {
782+
os << ",obsoleted=" << AvAttr->Obsoleted.getValue().getAsString();
783+
}
784+
if (!AvAttr->Rename.empty()) {
785+
// NB: Don't bother getting obj-c names, we can't get one for the rename
786+
os << ",message=\"'" << VD->getName() << "' has been renamed to '";
787+
printEncodedString(AvAttr->Rename, false);
788+
os << '\'';
789+
if (!AvAttr->Message.empty()) {
790+
os << ": ";
791+
printEncodedString(AvAttr->Message, false);
792+
}
793+
os << "\"";
794+
} else if (!AvAttr->Message.empty()) {
795+
os << ",message=";
796+
printEncodedString(AvAttr->Message);
797+
}
798+
}
799+
os << ")";
800+
}
801+
}
802+
}
637803

638804
void visitFuncDecl(FuncDecl *FD) {
639805
if (FD->getDeclContext()->isTypeContext())
@@ -2236,6 +2402,18 @@ class ModuleWriter {
22362402
"#if !defined(SWIFT_UNAVAILABLE)\n"
22372403
"# define SWIFT_UNAVAILABLE __attribute__((unavailable))\n"
22382404
"#endif\n"
2405+
"#if !defined(SWIFT_UNAVAILABLE_MSG)\n"
2406+
"# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))\n"
2407+
"#endif\n"
2408+
"#if !defined(SWIFT_AVAILABILITY)\n"
2409+
"# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))\n"
2410+
"#endif\n"
2411+
"#if !defined(SWIFT_DEPRECATED)\n"
2412+
"# define SWIFT_DEPRECATED __attribute__((deprecated))\n"
2413+
"#endif\n"
2414+
"#if !defined(SWIFT_DEPRECATED_MSG)\n"
2415+
"# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))\n"
2416+
"#endif\n"
22392417
;
22402418
static_assert(SWIFT_MAX_IMPORTED_SIMD_ELEMENTS == 4,
22412419
"need to add SIMD typedefs here if max elements is increased");
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %s -disable-objc-attr-requires-foundation-module
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -parse-as-library %t/availability.swiftmodule -typecheck -emit-objc-header-path %t/availability.h -import-objc-header %S/../Inputs/empty.h -disable-objc-attr-requires-foundation-module
5+
// RUN: %FileCheck %s < %t/availability.h
6+
// RUN: %check-in-clang %t/availability.h
7+
8+
// REQUIRES: objc_interop
9+
10+
// CHECK-LABEL: @interface Availability{{$}}
11+
// CHECK-NEXT: - (void)alwaysAvailable;
12+
// CHECK-NEXT: - (void)alwaysUnavailable SWIFT_UNAVAILABLE;
13+
// CHECK-NEXT: - (void)alwaysUnavailableTwo SWIFT_UNAVAILABLE_MSG("stuff happened");
14+
// CHECK-NEXT: - (void)alwaysUnavailableThree SWIFT_UNAVAILABLE_MSG("'alwaysUnavailableThree' has been renamed to 'bar'");
15+
// CHECK-NEXT: - (void)alwaysUnavailableFour SWIFT_UNAVAILABLE_MSG("'alwaysUnavailableFour' has been renamed to 'baz': whatever");
16+
// CHECK-NEXT: - (void)alwaysDeprecated SWIFT_DEPRECATED;
17+
// CHECK-NEXT: - (void)alwaysDeprecatedTwo SWIFT_DEPRECATED_MSG("it's old");
18+
// CHECK-NEXT: - (void)alwaysDeprecatedThree SWIFT_DEPRECATED_MSG("", "qux");
19+
// CHECK-NEXT: - (void)alwaysDeprecatedFour SWIFT_DEPRECATED_MSG("use something else", "quux");
20+
// CHECK-NEXT: - (void)escapeMessage SWIFT_DEPRECATED_MSG("one\ntwo\tthree\x0Dfour\\ \"five\"");
21+
// CHECK-NEXT: - (void)unicodeMessage SWIFT_DEPRECATED_MSG("über");
22+
// CHECK-NEXT: - (void)singlePlatShorthand SWIFT_AVAILABILITY(macos,introduced=10.10);
23+
// CHECK-NEXT: - (void)multiPlatShorthand
24+
// CHECK-DAG: SWIFT_AVAILABILITY(macos,introduced=10.11)
25+
// CHECK-DAG: SWIFT_AVAILABILITY(ios,introduced=9.0)
26+
// CHECK-DAG: SWIFT_AVAILABILITY(tvos,introduced=9.0)
27+
// CHECK-DAG: SWIFT_AVAILABILITY(watchos,introduced=3.0)
28+
// CHECK-NEXT: - (void)singlePlatIntroduced SWIFT_AVAILABILITY(ios,introduced=9.0);
29+
// CHECK-NEXT: - (void)singlePlatDeprecated SWIFT_AVAILABILITY(macos,deprecated=10.10);
30+
// CHECK-NEXT: - (void)singlePlatDeprecatedTwo SWIFT_AVAILABILITY(macos,deprecated=10.10,message="'singlePlatDeprecatedTwo' has been renamed to 'flubber'");
31+
// CHECK-NEXT: - (void)singlePlatDeprecatedThree SWIFT_AVAILABILITY(macos,deprecated=10.10,message="'singlePlatDeprecatedThree' has been renamed to 'fozzybear': we changed our minds");
32+
// CHECK-NEXT: - (void)singlePlatDeprecatedAlways SWIFT_AVAILABILITY(tvos,deprecated=0.0.1);
33+
// CHECK-NEXT: - (void)singlePlatDeprecatedAlwaysTwo SWIFT_AVAILABILITY(macos,introduced=10.7,deprecated=10.7);
34+
// CHECK-NEXT: - (void)singlePlatUnavailable SWIFT_AVAILABILITY(watchos,unavailable);
35+
// CHECK-NEXT: - (void)singlePlatUnavailableTwo SWIFT_AVAILABILITY(watchos,unavailable);
36+
// CHECK-NEXT: - (void)singlePlatObsoleted SWIFT_AVAILABILITY(ios,obsoleted=8.1);
37+
// CHECK-NEXT: - (void)singlePlatCombined SWIFT_AVAILABILITY(macos,introduced=10.7,deprecated=10.9,obsoleted=10.10);
38+
// CHECK-NEXT: - (void)multiPlatCombined
39+
// CHECK-DAG: SWIFT_AVAILABILITY(macos,introduced=10.6,deprecated=10.8,obsoleted=10.9)
40+
// CHECK-DAG: SWIFT_AVAILABILITY(ios,introduced=7.0,deprecated=9.0,obsoleted=10.0)
41+
// CHECK-NEXT: - (void)extensionUnavailable
42+
// CHECK-DAG: SWIFT_AVAILABILITY(macos_app_extension,unavailable)
43+
// CHECK-DAG: SWIFT_AVAILABILITY(ios_app_extension,unavailable)
44+
// CHECK-DAG: SWIFT_AVAILABILITY(tvos_app_extension,unavailable)
45+
// CHECK-DAG: SWIFT_AVAILABILITY(watchos_app_extension,unavailable)
46+
// CHECK-NEXT: - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
47+
// CHECK-NEXT: - (nonnull instancetype)initWithX:(NSInteger)_ OBJC_DESIGNATED_INITIALIZER SWIFT_AVAILABILITY(macos,introduced=10.10);
48+
// CHECK-NEXT: @end
49+
// CHECK-LABEL: @interface AvailabilitySub
50+
// CHECK-NEXT: - (nonnull instancetype)init SWIFT_UNAVAILABLE;
51+
// CHECK-NEXT: - (nonnull instancetype)initWithX:(NSInteger)_ SWIFT_UNAVAILABLE;
52+
// CHECK-NEXT: @end
53+
@objc class Availability {
54+
func alwaysAvailable() {}
55+
56+
@available(*, unavailable)
57+
func alwaysUnavailable() {}
58+
@available(*, unavailable, message: "stuff happened")
59+
func alwaysUnavailableTwo() {}
60+
@available(*, unavailable, renamed: "bar")
61+
func alwaysUnavailableThree() {}
62+
@available(*, unavailable, message: "whatever", renamed: "baz")
63+
func alwaysUnavailableFour() {}
64+
65+
@available(*, deprecated)
66+
func alwaysDeprecated() {}
67+
@available(*, deprecated, message: "it's old")
68+
func alwaysDeprecatedTwo() {}
69+
@available(*, deprecated, renamed: "qux")
70+
func alwaysDeprecatedThree() {}
71+
@available(*, deprecated, message: "use something else", renamed: "quux")
72+
func alwaysDeprecatedFour() {}
73+
74+
@available(*, deprecated, message: "one\ntwo\tthree\rfour\\ \"five\"")
75+
func escapeMessage() {}
76+
@available(*, deprecated, message: "über")
77+
func unicodeMessage() {}
78+
79+
@available(macOS 10.10, *)
80+
func singlePlatShorthand() {}
81+
@available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 3.0, *)
82+
func multiPlatShorthand() {}
83+
84+
@available(iOS, introduced: 9.0)
85+
func singlePlatIntroduced() {}
86+
@available(macOS, deprecated: 10.10)
87+
func singlePlatDeprecated() {}
88+
@available(macOS, deprecated: 10.10, renamed: "flubber")
89+
func singlePlatDeprecatedTwo() {}
90+
@available(macOS, deprecated: 10.10, message: "we changed our minds", renamed: "fozzybear")
91+
func singlePlatDeprecatedThree() {}
92+
@available(tvOS, deprecated)
93+
func singlePlatDeprecatedAlways() {}
94+
@available(macOS, introduced: 10.7, deprecated)
95+
func singlePlatDeprecatedAlwaysTwo() {}
96+
@available(watchOS, unavailable)
97+
func singlePlatUnavailable() {}
98+
@available(watchOS, introduced: 2.0, unavailable)
99+
func singlePlatUnavailableTwo() {}
100+
@available(iOS, obsoleted: 8.1)
101+
func singlePlatObsoleted() {}
102+
@available(macOS, introduced: 10.7, deprecated: 10.9, obsoleted: 10.10)
103+
func singlePlatCombined() {}
104+
105+
@available(macOS, introduced: 10.6, deprecated: 10.8, obsoleted: 10.9)
106+
@available(iOS, introduced: 7.0, deprecated: 9.0, obsoleted: 10.0)
107+
func multiPlatCombined() {}
108+
109+
@available(macOSApplicationExtension, unavailable)
110+
@available(iOSApplicationExtension, unavailable)
111+
@available(tvOSApplicationExtension, unavailable)
112+
@available(watchOSApplicationExtension, unavailable)
113+
func extensionUnavailable() {}
114+
115+
init() {}
116+
@available(macOS 10.10, *)
117+
init(x _: Int) {}
118+
}
119+
120+
@objc class AvailabilitySub: Availability {
121+
private override init() { super.init() }
122+
@available(macOS 10.10, *)
123+
private override init(x _: Int) { super.init() }
124+
}

0 commit comments

Comments
 (0)