Skip to content

Add availability macro support to @_backDeploy #41734

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
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
30 changes: 0 additions & 30 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1561,46 +1561,16 @@ ERROR(attr_availability_duplicate,none,
(StringRef, StringRef))

// originallyDefinedIn
// FIXME(backDeploy): Refactor to share with back deployment attr
ERROR(originally_defined_in_missing_rparen,none,
"expected ')' in @_originallyDefinedIn argument list", ())

// FIXME(backDeploy): Refactor to share with back deployment attr
ERROR(originally_defined_in_unrecognized_platform,none,
"unrecognized platform name in @_originallyDefinedIn argument list", ())

// FIXME: This is unused and can be removed
ERROR(originally_defined_in_unrecognized_property,none,
"unrecognized property in @_originallyDefinedIn argument list", ())

ERROR(originally_defined_in_need_original_module_name,none,
"expected 'module: \"original\"' in the first argument to "
"@_originallyDefinedIn", ())

ERROR(originally_defined_in_need_nonempty_module_name,none,
"original module name cannot be empty in @_originallyDefinedIn", ())

// FIXME(backDeploy): Refactor to share with back deployment attr
ERROR(originally_defined_in_need_platform_version,none,
"expected at least one platform version in @_originallyDefinedIn", ())

// FIXME(backDeploy): Refactor to share with back deployment attr
WARNING(originally_defined_in_major_minor_only,none,
"@_originallyDefinedIn only uses major and minor version number", ())

// FIXME(backDeploy): Refactor to share with back deployment attr
WARNING(originally_defined_in_missing_platform_name,none,
"* as platform name has no effect", ())

// FIXME(backDeploy): Refactor to share with back deployment attr
WARNING(originally_defined_in_swift_version, none,
"Swift language version checks has no effect "
"in @_originallyDefinedIn", ())

WARNING(originally_defined_in_package_description, none,
"PackageDescription version checks has no effect "
"in @_originallyDefinedIn", ())

// backDeploy
ERROR(attr_back_deploy_missing_rparen,none,
"expected ')' in '@_backDeploy' argument list", ())
Expand Down
155 changes: 50 additions & 105 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1743,34 +1743,59 @@ void Parser::parseAllAvailabilityMacroArguments() {

ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName,
llvm::SmallVector<PlatformAndVersion, 4> &PlatformAndVersions) {
// FIXME(backDeploy): Parse availability macros (e.g. SwiftStdlib: 5.1)
SyntaxParsingContext argumentContext(SyntaxContext,
SyntaxKind::AvailabilityVersionRestriction);

// Check for availability macros first.
if (peekAvailabilityMacroName()) {
SmallVector<AvailabilitySpec *, 4> Specs;
ParserStatus MacroStatus = parseAvailabilityMacro(Specs);
if (MacroStatus.isError())
return MacroStatus;

for (auto *Spec : Specs) {
auto *PlatformVersionSpec =
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec);
// Since peekAvailabilityMacroName() only matches defined availability
// macros, we don't expect to get any other kind of spec here.
assert(PlatformVersionSpec && "Unexpected AvailabilitySpec kind");

auto Platform = PlatformVersionSpec->getPlatform();
auto Version = PlatformVersionSpec->getVersion();
if (Version.getSubminor().hasValue() || Version.getBuild().hasValue()) {
diagnose(PlatformVersionSpec->getVersionSrcRange().Start,
diag::attr_availability_platform_version_major_minor_only,
AttrName);
}
PlatformAndVersions.emplace_back(Platform, Version);
}

return makeParserSuccess();
}

// Expect a possible platform name (e.g. 'macOS' or '*').
if (!Tok.isAny(tok::identifier, tok::oper_binary_spaced)) {
diagnose(Tok, diag::attr_availability_expected_platform, AttrName);
return makeParserError();
}

// Parse the platform name.
auto MaybePlatform = platformFromString(Tok.getText());
StringRef platformText = Tok.getText();
auto MaybePlatform = platformFromString(platformText);
SourceLoc PlatformLoc = Tok.getLoc();
if (!MaybePlatform.hasValue()) {
diagnose(PlatformLoc, diag::attr_availability_unknown_platform,
Tok.getText(), AttrName);
return makeParserError();
}
consumeToken();
PlatformKind Platform = *MaybePlatform;

// Wildcards ('*') aren't supported in this kind of list. If this list
// entry is just a wildcard, skip it. Wildcards with a version are
// diagnosed below.
if (Platform == PlatformKind::none && Tok.isAny(tok::comma, tok::r_paren)) {
if (!MaybePlatform.hasValue()) {
diagnose(PlatformLoc, diag::attr_availability_unknown_platform,
platformText, AttrName);
} else if (*MaybePlatform == PlatformKind::none) {
// Wildcards ('*') aren't supported in this kind of list.
diagnose(PlatformLoc, diag::attr_availability_wildcard_ignored,
AttrName);
return makeParserSuccess();

// If this list entry is just a wildcard, skip it.
if (Tok.isAny(tok::comma, tok::r_paren))
return makeParserSuccess();
}

// Parse version number.
Expand All @@ -1789,13 +1814,13 @@ ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName,
AttrName);
}

// Wildcards ('*') aren't supported in this kind of list.
if (Platform == PlatformKind::none) {
diagnose(PlatformLoc, diag::attr_availability_wildcard_ignored,
AttrName);
} else {
PlatformAndVersions.emplace_back(Platform, VerTuple);
if (MaybePlatform.hasValue()) {
auto Platform = *MaybePlatform;
if (Platform != PlatformKind::none) {
PlatformAndVersions.emplace_back(Platform, VerTuple);
}
}

return makeParserSuccess();
}

Expand Down Expand Up @@ -2448,91 +2473,11 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
}
// Parse 'OSX 13.13'.
case NextSegmentKind::PlatformVersion: {
SyntaxParsingContext argumentContext(SyntaxContext,
SyntaxKind::AvailabilityVersionRestriction);
if ((Tok.is(tok::identifier) || Tok.is(tok::oper_binary_spaced)) &&
(peekToken().isAny(tok::integer_literal, tok::floating_literal) ||
peekAvailabilityMacroName())) {

PlatformKind Platform;

if (peekAvailabilityMacroName()) {
// Handle availability macros first.
//
// The logic to search for macros and platform name could
// likely be handled by parseAvailabilitySpecList
// if we don't rely on parseList here.
SmallVector<AvailabilitySpec *, 4> Specs;
ParserStatus MacroStatus = parseAvailabilityMacro(Specs);
if (MacroStatus.isError())
return MacroStatus;

for (auto *Spec : Specs) {
if (auto *PlatformVersionSpec =
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec)) {
auto Platform = PlatformVersionSpec->getPlatform();
auto Version = PlatformVersionSpec->getVersion();
if (Version.getSubminor().hasValue() ||
Version.getBuild().hasValue()) {
diagnose(Tok.getLoc(), diag::originally_defined_in_major_minor_only);
}
PlatformAndVersions.emplace_back(Platform, Version);

} else if (auto *PlatformAgnostic =
dyn_cast<PlatformAgnosticVersionConstraintAvailabilitySpec>(Spec)) {
diagnose(PlatformAgnostic->getPlatformAgnosticNameLoc(),
PlatformAgnostic->isLanguageVersionSpecific() ?
diag::originally_defined_in_swift_version :
diag::originally_defined_in_package_description);

} else if (auto *OtherPlatform =
dyn_cast<OtherPlatformAvailabilitySpec>(Spec)) {
diagnose(OtherPlatform->getStarLoc(),
diag::originally_defined_in_missing_platform_name);

} else {
llvm_unreachable("Unexpected AvailabilitySpec kind.");
}
}

return makeParserSuccess();
}

// Parse platform name.
auto Plat = platformFromString(Tok.getText());
if (!Plat.hasValue()) {
diagnose(Tok.getLoc(),
diag::originally_defined_in_unrecognized_platform);
SuppressLaterDiags = true;
return makeParserError();
} else {
consumeToken();
Platform = *Plat;
}
// Parse version number
llvm::VersionTuple VerTuple;
SourceRange VersionRange;
if (parseVersionTuple(VerTuple, VersionRange,
Diagnostic(diag::attr_availability_expected_version, AttrName))) {
SuppressLaterDiags = true;
return makeParserError();
} else {
if (VerTuple.getSubminor().hasValue() ||
VerTuple.getBuild().hasValue()) {
diagnose(Tok.getLoc(), diag::originally_defined_in_major_minor_only);
}
// * as platform name isn't supported.
if (Platform == PlatformKind::none) {
diagnose(AtLoc, diag::originally_defined_in_missing_platform_name);
} else {
PlatformAndVersions.emplace_back(Platform, VerTuple);
}
return makeParserSuccess();
}
}
diagnose(AtLoc, diag::originally_defined_in_need_platform_version);
SuppressLaterDiags = true;
return makeParserError();
ParserStatus ListItemStatus =
parsePlatformVersionInList(AttrName, PlatformAndVersions);
if (ListItemStatus.isErrorOrHasCompletion())
SuppressLaterDiags = true;
return ListItemStatus;
}
}
llvm_unreachable("invalid next segment kind");
Expand All @@ -2544,7 +2489,7 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
return false;
}
if (PlatformAndVersions.empty()) {
diagnose(AtLoc, diag::originally_defined_in_need_platform_version);
diagnose(AtLoc, diag::attr_availability_need_platform_version, AttrName);
return false;
}

Expand Down
31 changes: 25 additions & 6 deletions test/ModuleInterface/back-deploy-attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

// Ensure @_backDeploy attributes and function bodies are printed in
// swiftinterface files.
// RUN: %target-swiftc_driver -emit-module -o %t/Test.swiftmodule -emit-module-interface-path %t/Test.swiftinterface -enable-library-evolution -verify-emitted-module-interface -module-name Test %s
// RUN: %target-swiftc_driver -emit-module -o %t/Test.swiftmodule -emit-module-interface-path %t/Test.swiftinterface %s -enable-library-evolution -verify-emitted-module-interface -module-name Test \
// RUN: -Xfrontend -define-availability -Xfrontend "_macOS12_1:macOS 12.1" \
// RUN: -Xfrontend -define-availability -Xfrontend "_myProject 1.0:macOS 12.1, iOS 15.1"
// RUN: %FileCheck %s --check-prefix FROMSOURCE --check-prefix CHECK < %t/Test.swiftinterface

// FIXME(backDeploy): Remove this step in favor of a test that exercises using
// a back deployed API from a test library so that we can avoid -merge-modules

// Ensure @_backDeploy attributes and function bodies are present after
// deserializing .swiftmodule files.
// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules %t/Test.swiftmodule -disable-objc-attr-requires-foundation-module -emit-module-interface-path %t/TestFromModule.swiftinterface -module-name Test
// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules %t/Test.swiftmodule -disable-objc-attr-requires-foundation-module -emit-module-interface-path %t/TestFromModule.swiftinterface -module-name Test \
// RUN: -define-availability "_macOS12_1:macOS 12.1" \
// RUN: -define-availability "_myProject 1.0:macOS 12.1, iOS 15.1"
// RUN: %FileCheck %s --check-prefix FROMMODULE --check-prefix CHECK < %t/TestFromModule.swiftinterface

public struct TopLevelStruct {
Expand Down Expand Up @@ -66,10 +70,25 @@ public struct TopLevelStruct {
}

// CHECK: @_backDeploy(macOS 12.0)
// FROMSOURCE: public func backDeployTopLevelFunc() -> Swift.Int { return 47 }
// FROMMODULE: public func backDeployTopLevelFunc() -> Swift.Int
// FROMSOURCE: public func backDeployTopLevelFunc1() -> Swift.Int { return 47 }
// FROMMODULE: public func backDeployTopLevelFunc1() -> Swift.Int
@available(macOS 11.0, *)
@_backDeploy(macOS 12.0)
public func backDeployTopLevelFunc() -> Int { return 47 }
public func backDeployTopLevelFunc1() -> Int { return 47 }

// FIXME(backDeploy): Availability macros should be supported
// MARK: - Availability macros

// CHECK: @_backDeploy(macOS 12.1)
// FROMSOURCE: public func backDeployTopLevelFunc2() -> Swift.Int { return 48 }
// FROMMODULE: public func backDeployTopLevelFunc2() -> Swift.Int
@available(macOS 11.0, *)
@_backDeploy(_macOS12_1)
public func backDeployTopLevelFunc2() -> Int { return 48 }

// CHECK: @_backDeploy(macOS 12.1)
// CHECK: @_backDeploy(iOS 15.1)
// FROMSOURCE: public func backDeployTopLevelFunc3() -> Swift.Int { return 49 }
// FROMMODULE: public func backDeployTopLevelFunc3() -> Swift.Int
@available(macOS 11.0, iOS 14.0, *)
@_backDeploy(_myProject 1.0)
public func backDeployTopLevelFunc3() -> Int { return 49 }
11 changes: 6 additions & 5 deletions test/Parse/original_defined_in_attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ public func foo() {}
@_originallyDefinedIn(modulename: "foo", OSX 13.13) // expected-error {{expected 'module: "original"' in the first argument to @_originallyDefinedIn}}
public func foo1() {}

@_originallyDefinedIn(module: "foo", OSX 13.13.3) // expected-warning {{@_originallyDefinedIn only uses major and minor version number}} expected-error {{'@_originallyDefinedIn' requires that 'ToplevelClass' have explicit availability for macOS}}
@_originallyDefinedIn(module: "foo", OSX 13.13.3) // expected-warning {{'@_originallyDefinedIn' only uses major and minor version number}}
// expected-error@-1 {{'@_originallyDefinedIn' requires that 'ToplevelClass' have explicit availability for macOS}}
public class ToplevelClass {}

@_originallyDefinedIn(module: "foo") // expected-error {{expected at least one platform version in @_originallyDefinedIn}}
@_originallyDefinedIn(module: "foo") // expected-error {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
public class ToplevelClass1 {}

@_originallyDefinedIn(OSX 13.13.3) // expected-error {{expected 'module: "original"' in the first argument to @_originallyDefinedIn}}
public class ToplevelClass2 {}

@_originallyDefinedIn(module: "foo", // expected-error {{expected at least one platform version in @_originallyDefinedIn}}
public class ToplevelClass3 {}
@_originallyDefinedIn(module: "foo",
public class ToplevelClass3 {} // expected-error {{expected platform in '@_originallyDefinedIn' attribute}}

@available(OSX 13.10, *)
@_originallyDefinedIn(module: "foo", * 13.13) // expected-warning {{* as platform name has no effect}} expected-error {{expected at least one platform version in @_originallyDefinedIn}}
@_originallyDefinedIn(module: "foo", * 13.13) // expected-warning {{* as platform name has no effect}} expected-error {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
@_originallyDefinedIn(module: "foo", OSX 13.13, iOS 7.0)
@_originallyDefinedIn(module: "foo", OSX 13.14, * 7.0) // expected-warning {{* as platform name has no effect}} expected-error {{'@_originallyDefinedIn' contains multiple versions for macOS}}
public class ToplevelClass4 {
Expand Down
11 changes: 8 additions & 3 deletions test/Sema/diag_originally_definedin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ public class C {
public class D {}

@available(macOS 10.9, *)
@_originallyDefinedIn(module: "original", _myProject 2.0) // expected-error {{expected at least one platform version in @_originallyDefinedIn}}
// expected-error @-1 {{reference to undefined version '2.0' for availability macro '_myProject'}}
@_originallyDefinedIn(module: "original", _myProject 2.0) // expected-error {{reference to undefined version '2.0' for availability macro '_myProject'}}
public func macroVersionned() {}

// Fallback to the default diagnostic when the macro is unknown.
@available(macOS 10.9, *)
@_originallyDefinedIn(module: "original", _unknownMacro) // expected-error {{expected at least one platform version in @_originallyDefinedIn}}
@_originallyDefinedIn(module: "original", _unknownMacro) // expected-warning {{unknown platform '_unknownMacro' for attribute '@_originallyDefinedIn'}}
// expected-error@-1 {{expected version number in '@_originallyDefinedIn' attribute}}
public func macroUnknown() {}

@available(macOS 10.9, *)
@_originallyDefinedIn(module: "original", swift 5.1) // expected-warning {{unknown platform 'swift' for attribute '@_originallyDefinedIn'}}
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
public func swiftVersionMacro() {}
25 changes: 23 additions & 2 deletions test/attr/attr_backDeploy.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %target-typecheck-verify-swift -parse-as-library
// RUN: %target-typecheck-verify-swift -parse-as-library \
// RUN: -define-availability "_myProject 2.0:macOS 12.0"

// MARK: - Valid declarations

Expand Down Expand Up @@ -192,6 +193,7 @@ public func transparentFunc() {}

// MARK: - Attribute parsing

@available(macOS 11.0, *)
@_backDeploy(macOS 12.0, unknownOS 1.0) // expected-warning {{unknown platform 'unknownOS' for attribute '@_backDeploy'}}
public func unknownOSFunc() {}

Expand Down Expand Up @@ -230,8 +232,27 @@ public func trailingWildcardFunc() {}
@_backDeploy(macOS 12.0, *, iOS 15.0) // expected-warning {{* as platform name has no effect in '@_backDeploy' attribute}}
public func embeddedWildcardFunc() {}

@_backDeploy(_myProject 3.0) // expected-error {{reference to undefined version '3.0' for availability macro '_myProject'}}
public func macroVersionned() {}

@_backDeploy(_myProject) // expected-error {{reference to undefined version '0' for availability macro '_myProject'}}
public func missingMacroVersion() {}

// Fall back to the default diagnostic when the macro is unknown.
@_backDeploy(_unknownMacro) // expected-warning {{unknown platform '_unknownMacro' for attribute '@_backDeploy'}}
// expected-error@-1 {{expected version number in '@_backDeploy' attribute}}
public func unknownMacroMissingVersion() {}

@_backDeploy(_unknownMacro 1.0) // expected-warning {{unknown platform '_unknownMacro' for attribute '@_backDeploy'}}
// expected-error@-1 {{expected at least one platform version in '@_backDeploy' attribute}}
public func unknownMacroVersionned() {}

@available(macOS 11.0, *)
@_backDeploy(_unknownMacro 1.0, _myProject 2.0) // expected-warning {{unknown platform '_unknownMacro' for attribute '@_backDeploy'}}
public func knownAndUnknownMacroVersionned() {}

@_backDeploy() // expected-error {{expected at least one platform version in '@_backDeploy' attribute}}
public func zeroPlatformVersionsFunc() {}
public func emptyPlatformVersionsFunc() {}

@_backDeploy // expected-error {{expected '(' in '_backDeploy' attribute}}
public func expectedLeftParenFunc() {}
Expand Down