Skip to content

[PrintAsObjC] Print availability on classes, protocols, and categories #11252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 146 additions & 118 deletions lib/PrintAsObjC/PrintAsObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,13 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
StringRef customName = getNameForObjC(CD, CustomNamesOnly);
if (customName.empty()) {
llvm::SmallString<32> scratch;
os << "SWIFT_CLASS(\"" << CD->getObjCRuntimeName(scratch) << "\")\n"
<< "@interface " << CD->getName();
os << "SWIFT_CLASS(\"" << CD->getObjCRuntimeName(scratch) << "\")";
printAvailability(CD);
os << "\n@interface " << CD->getName();
} else {
os << "SWIFT_CLASS_NAMED(\"" << CD->getName() << "\")\n"
<< "@interface " << customName;
os << "SWIFT_CLASS_NAMED(\"" << CD->getName() << "\")";
printAvailability(CD);
os << "\n@interface " << customName;
}

if (Type superTy = CD->getSuperclass())
Expand Down Expand Up @@ -350,6 +352,8 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,

auto baseClass = ED->getExtendedType()->getClassOrBoundGenericClass();

if (printAvailability(ED, PrintLeadingSpace::No))
os << "\n";
os << "@interface " << getNameForObjC(baseClass);
maybePrintObjCGenericParameters(baseClass);
os << " (SWIFT_EXTENSION(" << ED->getModuleContext()->getName() << "))";
Expand All @@ -365,11 +369,13 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
StringRef customName = getNameForObjC(PD, CustomNamesOnly);
if (customName.empty()) {
llvm::SmallString<32> scratch;
os << "SWIFT_PROTOCOL(\"" << PD->getObjCRuntimeName(scratch) << "\")\n"
<< "@protocol " << PD->getName();
os << "SWIFT_PROTOCOL(\"" << PD->getObjCRuntimeName(scratch) << "\")";
printAvailability(PD);
os << "\n@protocol " << PD->getName();
} else {
os << "SWIFT_PROTOCOL_NAMED(\"" << PD->getName() << "\")\n"
<< "@protocol " << customName;
os << "SWIFT_PROTOCOL_NAMED(\"" << PD->getName() << "\")";
printAvailability(PD);
os << "\n@protocol " << customName;
}

printProtocols(PD->getInheritedProtocols());
Expand Down Expand Up @@ -626,7 +632,7 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
}

if (!skipAvailability) {
appendAvailabilityAttribute(AFD);
printAvailability(AFD);
}

if (isa<FuncDecl>(AFD) && cast<FuncDecl>(AFD)->isAccessor()) {
Expand Down Expand Up @@ -683,132 +689,154 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
os << " SWIFT_WARN_UNUSED_RESULT";
}

appendAvailabilityAttribute(FD);
printAvailability(FD);

os << ';';
}

void appendAvailabilityAttribute(const ValueDecl *VD) {
for (auto Attr : VD->getAttrs()) {
if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
if (AvAttr->isInvalid()) continue;
if (AvAttr->Platform == PlatformKind::none) {
if (AvAttr->PlatformAgnostic == PlatformAgnosticAvailabilityKind::Unavailable) {
// Availability for *
if (!AvAttr->Rename.empty()) {
// NB: Don't bother getting obj-c names, we can't get one for the
// rename
os << " SWIFT_UNAVAILABLE_MSG(\"'" << VD->getBaseName()
<< "' has been renamed to '";
printEncodedString(AvAttr->Rename, false);
os << '\'';
if (!AvAttr->Message.empty()) {
os << ": ";
printEncodedString(AvAttr->Message, false);
}
os << "\")";
} else if (!AvAttr->Message.empty()) {
os << " SWIFT_UNAVAILABLE_MSG(";
printEncodedString(AvAttr->Message);
os << ")";
} else {
os << " SWIFT_UNAVAILABLE";
}
break;
}
if (AvAttr->isUnconditionallyDeprecated()) {
if (!AvAttr->Rename.empty() || !AvAttr->Message.empty()) {
os << " SWIFT_DEPRECATED_MSG(";
printEncodedString(AvAttr->Message);
if (!AvAttr->Rename.empty()) {
os << ", ";
printEncodedString(AvAttr->Rename);
}
os << ")";
} else {
os << " SWIFT_DEPRECATED";
}
}
continue;
}
// Availability for a specific platform
if (!AvAttr->Introduced.hasValue()
&& !AvAttr->Deprecated.hasValue()
&& !AvAttr->Obsoleted.hasValue()
&& !AvAttr->isUnconditionallyDeprecated()
&& !AvAttr->isUnconditionallyUnavailable()) {
continue;
}
const char *plat = nullptr;
switch (AvAttr->Platform) {
case PlatformKind::OSX:
plat = "macos";
break;
case PlatformKind::iOS:
plat = "ios";
break;
case PlatformKind::tvOS:
plat = "tvos";
break;
case PlatformKind::watchOS:
plat = "watchos";
break;
case PlatformKind::OSXApplicationExtension:
plat = "macos_app_extension";
break;
case PlatformKind::iOSApplicationExtension:
plat = "ios_app_extension";
break;
case PlatformKind::tvOSApplicationExtension:
plat = "tvos_app_extension";
break;
case PlatformKind::watchOSApplicationExtension:
plat = "watchos_app_extension";
break;
default:
break;
}
if (!plat) continue;
os << " SWIFT_AVAILABILITY(" << plat;
if (AvAttr->isUnconditionallyUnavailable()) {
os << ",unavailable";
} else {
if (AvAttr->Introduced.hasValue()) {
os << ",introduced=" << AvAttr->Introduced.getValue().getAsString();
}
if (AvAttr->Deprecated.hasValue()) {
os << ",deprecated=" << AvAttr->Deprecated.getValue().getAsString();
} else if (AvAttr->isUnconditionallyDeprecated()) {
// We need to specify some version, we can't just say deprecated.
// We also can't deprecate it before it's introduced.
if (AvAttr->Introduced.hasValue()) {
os << ",deprecated=" << AvAttr->Introduced.getValue().getAsString();
} else {
os << ",deprecated=0.0.1";
}
}
if (AvAttr->Obsoleted.hasValue()) {
os << ",obsoleted=" << AvAttr->Obsoleted.getValue().getAsString();
}
if (!AvAttr->Rename.empty()) {
// NB: Don't bother getting obj-c names, we can't get one for the rename
os << ",message=\"'" << VD->getBaseName()
enum class PrintLeadingSpace : bool {
No = false,
Yes = true
};

/// Returns \c true if anything was printed.
bool printAvailability(
const Decl *D,
PrintLeadingSpace printLeadingSpace = PrintLeadingSpace::Yes) {
bool hasPrintedAnything = false;
auto maybePrintLeadingSpace = [&] {
if (printLeadingSpace == PrintLeadingSpace::Yes || hasPrintedAnything)
os << " ";
hasPrintedAnything = true;
};

for (auto AvAttr : D->getAttrs().getAttributes<AvailableAttr>()) {
if (AvAttr->Platform == PlatformKind::none) {
if (AvAttr->PlatformAgnostic == PlatformAgnosticAvailabilityKind::Unavailable) {
// Availability for *
if (!AvAttr->Rename.empty() && isa<ValueDecl>(D)) {
// NB: Don't bother getting obj-c names, we can't get one for the
// rename
maybePrintLeadingSpace();
os << "SWIFT_UNAVAILABLE_MSG(\"'"
<< cast<ValueDecl>(D)->getBaseName()
<< "' has been renamed to '";
printEncodedString(AvAttr->Rename, false);
os << '\'';
if (!AvAttr->Message.empty()) {
os << ": ";
printEncodedString(AvAttr->Message, false);
}
os << "\"";
os << "\")";
} else if (!AvAttr->Message.empty()) {
os << ",message=";
maybePrintLeadingSpace();
os << "SWIFT_UNAVAILABLE_MSG(";
printEncodedString(AvAttr->Message);
os << ")";
} else {
maybePrintLeadingSpace();
os << "SWIFT_UNAVAILABLE";
}
break;
}
if (AvAttr->isUnconditionallyDeprecated()) {
if (!AvAttr->Rename.empty() || !AvAttr->Message.empty()) {
maybePrintLeadingSpace();
os << "SWIFT_DEPRECATED_MSG(";
printEncodedString(AvAttr->Message);
if (!AvAttr->Rename.empty()) {
os << ", ";
printEncodedString(AvAttr->Rename);
}
os << ")";
} else {
maybePrintLeadingSpace();
os << "SWIFT_DEPRECATED";
}
}
continue;
}

// Availability for a specific platform
if (!AvAttr->Introduced.hasValue()
&& !AvAttr->Deprecated.hasValue()
&& !AvAttr->Obsoleted.hasValue()
&& !AvAttr->isUnconditionallyDeprecated()
&& !AvAttr->isUnconditionallyUnavailable()) {
continue;
}

const char *plat;
switch (AvAttr->Platform) {
case PlatformKind::OSX:
plat = "macos";
break;
case PlatformKind::iOS:
plat = "ios";
break;
case PlatformKind::tvOS:
plat = "tvos";
break;
case PlatformKind::watchOS:
plat = "watchos";
break;
case PlatformKind::OSXApplicationExtension:
plat = "macos_app_extension";
break;
case PlatformKind::iOSApplicationExtension:
plat = "ios_app_extension";
break;
case PlatformKind::tvOSApplicationExtension:
plat = "tvos_app_extension";
break;
case PlatformKind::watchOSApplicationExtension:
plat = "watchos_app_extension";
break;
case PlatformKind::none:
llvm_unreachable("handled above");
}

maybePrintLeadingSpace();
os << "SWIFT_AVAILABILITY(" << plat;
if (AvAttr->isUnconditionallyUnavailable()) {
os << ",unavailable";
} else {
if (AvAttr->Introduced.hasValue()) {
os << ",introduced=" << AvAttr->Introduced.getValue().getAsString();
}
if (AvAttr->Deprecated.hasValue()) {
os << ",deprecated=" << AvAttr->Deprecated.getValue().getAsString();
} else if (AvAttr->isUnconditionallyDeprecated()) {
// We need to specify some version, we can't just say deprecated.
// We also can't deprecate it before it's introduced.
if (AvAttr->Introduced.hasValue()) {
os << ",deprecated=" << AvAttr->Introduced.getValue().getAsString();
} else {
os << ",deprecated=0.0.1";
}
}
if (AvAttr->Obsoleted.hasValue()) {
os << ",obsoleted=" << AvAttr->Obsoleted.getValue().getAsString();
}
}
if (!AvAttr->Rename.empty() && isa<ValueDecl>(D)) {
// NB: Don't bother getting obj-c names, we can't get one for the rename
os << ",message=\"'" << cast<ValueDecl>(D)->getBaseName()
<< "' has been renamed to '";
printEncodedString(AvAttr->Rename, false);
os << '\'';
if (!AvAttr->Message.empty()) {
os << ": ";
printEncodedString(AvAttr->Message, false);
}
os << ")";
os << "\"";
} else if (!AvAttr->Message.empty()) {
os << ",message=";
printEncodedString(AvAttr->Message);
}
os << ")";
}
return hasPrintedAnything;
}

void printSwift3ObjCDeprecatedInference(ValueDecl *VD) {
Expand Down
48 changes: 48 additions & 0 deletions test/PrintAsObjC/availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
// CHECK-NEXT: - (void)multiPlatCombined
// CHECK-DAG: SWIFT_AVAILABILITY(macos,introduced=10.6,deprecated=10.8,obsoleted=10.9)
// CHECK-DAG: SWIFT_AVAILABILITY(ios,introduced=7.0,deprecated=9.0,obsoleted=10.0)
// CHECK-NEXT: - (void)platUnavailableMessage SWIFT_AVAILABILITY(macos,unavailable,message="help I'm trapped in an availability factory");
// CHECK-NEXT: - (void)platUnavailableRename SWIFT_AVAILABILITY(macos,unavailable,message="'platUnavailableRename' has been renamed to 'plea'");
// CHECK-NEXT: - (void)platUnavailableRenameWithMessage SWIFT_AVAILABILITY(macos,unavailable,message="'platUnavailableRenameWithMessage' has been renamed to 'anotherPlea': still trapped");
// CHECK-NEXT: - (void)extensionUnavailable
// CHECK-DAG: SWIFT_AVAILABILITY(macos_app_extension,unavailable)
// CHECK-DAG: SWIFT_AVAILABILITY(ios_app_extension,unavailable)
Expand All @@ -46,10 +49,31 @@
// CHECK-NEXT: - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
// CHECK-NEXT: - (nonnull instancetype)initWithX:(NSInteger)_ OBJC_DESIGNATED_INITIALIZER SWIFT_AVAILABILITY(macos,introduced=10.10);
// CHECK-NEXT: @end

// CHECK-LABEL: {{^}}SWIFT_AVAILABILITY(macos,introduced=999){{$}}
// CHECK-NEXT: @interface Availability (SWIFT_EXTENSION(availability))
// CHECK-NEXT: - (void)extensionAvailability:(WholeClassAvailability * _Nonnull)_;
// CHECK-NEXT: @end

// CHECK-LABEL: @interface AvailabilitySub
// CHECK-NEXT: - (nonnull instancetype)init SWIFT_UNAVAILABLE;
// CHECK-NEXT: - (nonnull instancetype)initWithX:(NSInteger)_ SWIFT_UNAVAILABLE;
// CHECK-NEXT: @end

// CHECK-LABEL: SWIFT_CLASS("{{.+}}WholeClassAvailability")
// CHECK-SAME: SWIFT_AVAILABILITY(macos,introduced=999)
// CHECK-NEXT: @interface WholeClassAvailability
// CHECK-NEXT: - (void)wholeClassAvailability:(id <WholeProtoAvailability> _Nonnull)_;
// CHECK-NEXT: - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
// CHECK-NEXT: @end

// CHECK-LABEL: SWIFT_PROTOCOL("{{.+}}WholeProtoAvailability{{.*}}")
// CHECK-SAME: SWIFT_AVAILABILITY(macos,introduced=999)
// CHECK-NEXT: @protocol WholeProtoAvailability
// CHECK-NEXT: - (void)wholeProtoAvailability:(WholeClassAvailability * _Nonnull)_;
// CHECK-NEXT: @end


@objc class Availability {
@objc func alwaysAvailable() {}

Expand Down Expand Up @@ -106,6 +130,13 @@
@available(iOS, introduced: 7.0, deprecated: 9.0, obsoleted: 10.0)
@objc func multiPlatCombined() {}

@available(macOS, unavailable, message: "help I'm trapped in an availability factory")
@objc func platUnavailableMessage() {}
@available(macOS, unavailable, renamed: "plea")
@objc func platUnavailableRename() {}
@available(macOS, unavailable, renamed: "anotherPlea", message: "still trapped")
@objc func platUnavailableRenameWithMessage() {}

@available(macOSApplicationExtension, unavailable)
@available(iOSApplicationExtension, unavailable)
@available(tvOSApplicationExtension, unavailable)
Expand All @@ -117,8 +148,25 @@
@objc init(x _: Int) {}
}

// Deliberately a high number that the default deployment target will not reach.
@available(macOS 999, *)
extension Availability {
@objc func extensionAvailability(_: WholeClassAvailability) {}
}

@objc class AvailabilitySub: Availability {
private override init() { super.init() }
@available(macOS 10.10, *)
private override init(x _: Int) { super.init() }
}


@available(macOS 999, *)
@objc class WholeClassAvailability {
func wholeClassAvailability(_: WholeProtoAvailability) {}
}

@available(macOS 999, *)
@objc protocol WholeProtoAvailability {
func wholeProtoAvailability(_: WholeClassAvailability)
}