Skip to content

AST: Emit correct synthesized availability attributes for unownedExecutor property #64015

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 3 commits into from
Mar 3, 2023
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
4 changes: 4 additions & 0 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ class AvailabilityContext {

class AvailabilityInference {
public:
/// Returns the decl that should be considered the parent decl of the given
/// decl when looking for inherited availability annotations.
static const Decl *parentDeclForInferredAvailability(const Decl *D);

/// Infers the common availability required to access an array of
/// declarations and adds attributes reflecting that availability
/// to ToDecl.
Expand Down
19 changes: 15 additions & 4 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,22 @@ static AvailableAttr *createAvailableAttr(PlatformKind Platform,
StringRef Rename,
ValueDecl *RenameDecl,
ASTContext &Context) {

llvm::VersionTuple Introduced =
Inferred.Introduced.value_or(llvm::VersionTuple());
llvm::VersionTuple Deprecated =
Inferred.Deprecated.value_or(llvm::VersionTuple());
llvm::VersionTuple Obsoleted =
Inferred.Obsoleted.value_or(llvm::VersionTuple());

// If a decl is unavailable then it cannot have any introduced, deprecated, or
// obsoleted version.
if (Inferred.PlatformAgnostic ==
PlatformAgnosticAvailabilityKind::Unavailable) {
Introduced = llvm::VersionTuple();
Deprecated = llvm::VersionTuple();
Obsoleted = llvm::VersionTuple();
}

return new (Context)
AvailableAttr(SourceLoc(), SourceRange(), Platform,
Message, Rename, RenameDecl,
Expand Down Expand Up @@ -165,7 +173,8 @@ void AvailabilityInference::applyInferredAvailableAttrs(

/// Returns the decl that should be considered the parent decl of the given decl
/// when looking for inherited availability annotations.
static Decl *parentDeclForAvailability(const Decl *D) {
const Decl *
AvailabilityInference::parentDeclForInferredAvailability(const Decl *D) {
if (auto *AD = dyn_cast<AccessorDecl>(D))
return AD->getStorage();

Expand Down Expand Up @@ -230,7 +239,8 @@ SemanticAvailableRangeAttrRequest::evaluate(Evaluator &evaluator,
decl, decl->getASTContext()))
return std::make_pair(attr, decl);

if (auto *parent = parentDeclForAvailability(decl))
if (auto *parent =
AvailabilityInference::parentDeclForInferredAvailability(decl))
return parent->getSemanticAvailableRangeAttr();

return None;
Expand Down Expand Up @@ -262,7 +272,8 @@ SemanticUnavailableAttrRequest::evaluate(Evaluator &evaluator,
if (auto attr = decl->getAttrs().getUnavailable(decl->getASTContext()))
return std::make_pair(attr, decl);

if (auto *parent = parentDeclForAvailability(decl))
if (auto *parent =
AvailabilityInference::parentDeclForInferredAvailability(decl))
return parent->getSemanticUnavailableAttr();

return None;
Expand Down
8 changes: 6 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,12 @@ unsigned Decl::getAttachedMacroDiscriminator(
}

const Decl *Decl::getInnermostDeclWithAvailability() const {
if (auto attrAndDecl = getSemanticAvailableRangeAttr())
return attrAndDecl.value().second;
if (getAttrs().hasAttribute<AvailableAttr>())
return this;

if (auto parent =
AvailabilityInference::parentDeclForInferredAvailability(this))
return parent->getInnermostDeclWithAvailability();

return nullptr;
}
Expand Down
34 changes: 29 additions & 5 deletions test/Concurrency/concurrency_availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// RUN: %target-swift-frontend -parse-stdlib -target x86_64-apple-macosx12 -typecheck %s -DTARGET_MACOS_12
// REQUIRES: OS=macosx

import _Concurrency

func f() async { } // expected-error{{concurrency is only available in}}
// expected-note@-1{{add @available}}

Expand All @@ -13,19 +15,41 @@ actor A { } // expected-error{{concurrency is only available in}}
public func swift_deletedAsyncMethodError() async {
}

// Ensure that our synthesis of the actor's unownedExecutor does not cause
// availability errors.
@available(macOS 12.0, *)
struct S {
actor A {
// Ensure that our synthesis of the actor's unownedExecutor does not cause
// availability errors.
actor NestedActor {
}

// The synthesized unownedExecutor inside this actor should inherit the
// un-availability of UnavailableActor.
@available(macOS, unavailable)
actor UnavailableActor {
}
}

#if TARGET_MACOS_12
// The synthesized unownedExecutor inside this extension on S should inherit
// availability from S to avoid availability errors.
#if TARGET_MACOS_12
extension S {
actor A2 {
actor ExtensionNestedActor {
}
}
#endif

// Make sure that the conformances to Actor are actually being synthesized
// since otherwise this test isn't actually testing what it is designed to test.
@available(macOS 10.15, *)
func takesExecutor(_ e: UnownedSerialExecutor) { }

@available(macOS 12.0, *)
func testNestedActorConformance(_ a: S.NestedActor) {
takesExecutor(a.unownedExecutor)
}

#if TARGET_MACOS_12
func testExtensionNestedActorConformance(_ a: S.ExtensionNestedActor) {
takesExecutor(a.unownedExecutor)
}
#endif
68 changes: 68 additions & 0 deletions test/ModuleInterface/actor_availability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-emit-module-interface(%t/Library.swiftinterface) %s -module-name Library -target %target-swift-abi-5.3-triple
// RUN: %target-swift-typecheck-module-from-interface(%t/Library.swiftinterface) -module-name Library
// RUN: %FileCheck %s < %t/Library.swiftinterface

// REQUIRES: VENDOR=apple

// CHECK: #if compiler(>=5.3) && $Actors
// CHECK-NEXT: public actor ActorWithImplicitAvailability {
public actor ActorWithImplicitAvailability {
// CHECK: @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
// CHECK: #endif

// CHECK: #if compiler(>=5.3) && $Actors
// CHECK-NEXT: @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *)
// CHECK-NEXT: public actor ActorWithExplicitAvailability {
@available(SwiftStdlib 5.2, *)
public actor ActorWithExplicitAvailability {
// CHECK: @available(iOS 13.4, tvOS 13.4, watchOS 6.2, macOS 10.15.4, *)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
// CHECK: #endif

// CHECK: #if compiler(>=5.3) && $Actors
// CHECK-NEXT: @_hasMissingDesignatedInitializers @available(macOS, unavailable)
// CHECK-NEXT: public actor UnavailableActor {
@available(macOS, unavailable)
public actor UnavailableActor {
// CHECK: @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
// CHECK-NEXT: @available(macOS, unavailable)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
// CHECK: #endif

// CHECK: @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *)
// CHECK-NEXT: public enum Enum {
@available(SwiftStdlib 5.2, *)
public enum Enum {
// CHECK: #if compiler(>=5.3) && $Actors
// CHECK-NEXT: @_hasMissingDesignatedInitializers public actor NestedActor {
public actor NestedActor {
// CHECK: @available(iOS 13.4, tvOS 13.4, watchOS 6.2, macOS 10.15.4, *)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
// CHECK: #endif
}

// CHECK: extension Library.Enum {
extension Enum {
// CHECK: #if compiler(>=5.3) && $Actors
// CHECK-NEXT: @_hasMissingDesignatedInitializers public actor ExtensionNestedActor {
public actor ExtensionNestedActor {
// CHECK: @available(iOS 13.4, tvOS 13.4, watchOS 6.2, macOS 10.15.4, *)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
}
7 changes: 3 additions & 4 deletions test/ModuleInterface/actor_protocol.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-emit-module-interface(%t/Library.swiftinterface) %s -disable-availability-checking -module-name Library
// RUN: %target-swift-typecheck-module-from-interface(%t/Library.swiftinterface) -disable-availability-checking -module-name Library
// RUN: %FileCheck --check-prefix CHECK-EXTENSION %s <%t/Library.swiftinterface
// RUN: %target-swift-emit-module-interface(%t/Library.swiftinterface) %s -module-name Library
// RUN: %target-swift-typecheck-module-from-interface(%t/Library.swiftinterface) -module-name Library
// RUN: %FileCheck --check-prefix CHECK %s <%t/Library.swiftinterface
// REQUIRES: concurrency

Expand All @@ -10,7 +9,7 @@
/// and not via some extension. The requirement is due to the unique
/// optimizations applied to the implementation of actors.

// CHECK-EXTENSION-NOT: extension {{.+}} : _Concurrency.Actor
// CHECK-NOT: extension {{.+}} : _Concurrency.Actor

// CHECK: public actor PlainActorClass {
@available(SwiftStdlib 5.1, *)
Expand Down
42 changes: 41 additions & 1 deletion test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -426,15 +426,55 @@ swift_version = lit_config.params.get('swift-version',
lit_config.note('Compiling with -swift-version ' + swift_version)
config.swift_test_options = '-swift-version ' + swift_version

# Load availability macros for known stdlib releases.
# Loads availability macros for known stdlib releases.
def load_availability_macros():
path = os.path.join(os.path.dirname(__file__), "../utils/availability-macros.def")
lines = open(path, 'r').read().splitlines()
pattern = re.compile(r"\s*(#.*)?")
return filter(lambda l: pattern.fullmatch(l) is None, lines)

# Returns a tuple with the Swift ABI version (e.g. '5.0') and the corresponding
# target triple (e.g. 'arm64-apple-ios12.2')
def availability_macro_to_swift_abi_target_triple(macro):
# Use a regex and split to pull out the components of an availability macro
# that is formatted like this:
# SwiftStdlib 5.0:macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2
matches = re.search(r'^[\s]*SwiftStdlib[\s]+([0-9\.]+)[\s]*:[\s]*(.+)', macro)
stdlib_vers = matches.group(1)
vers_by_platform = {}
for platform_vers in matches.group(2).split(', '):
components = platform_vers.split(' ')
vers_by_platform[components[0]] = components[1]

platform = {
'macosx': 'macOS',
'ios': 'iOS',
'maccatalyst': 'iOS',
'tvos': 'tvOS',
'watchos': 'watchOS'
}.get(run_os)

if platform is None:
return stdlib_vers, config.variant_triple

os_vers = vers_by_platform.get(platform)
if os_vers is None:
return stdlib_vers, config.variant_triple

if run_os == 'maccatalyst':
return stdlib_vers, '%s-%s-ios%s-macabi' % (run_cpu, run_vendor, os_vers)

return stdlib_vers, '%s-%s-%s%s%s' % (run_cpu, run_vendor, run_os,
os_vers, run_environment)

# Add availability macros to the default frontend/driver flags and create target
# triple substitutions for each stdlib version.
for macro in load_availability_macros():
config.swift_frontend_test_options += " -define-availability '{0}'".format(macro)
config.swift_driver_test_options += " -Xfrontend -define-availability -Xfrontend '{0}'".format(macro)
(stdlib_vers, triple) = availability_macro_to_swift_abi_target_triple(macro)
config.substitutions.append(('%target-swift-abi-{}-triple'.format(stdlib_vers), triple))


differentiable_programming = lit_config.params.get('differentiable_programming', None)
if differentiable_programming is not None:
Expand Down