Skip to content

Commit 04e4d97

Browse files
authored
Merge pull request #36670 from kavon/objc-effectful-properties
[concurrency] Allow ObjC completion-handler methods to be imported as effectful properties
2 parents 10b4e7a + 1586ca8 commit 04e4d97

File tree

11 files changed

+396
-14
lines changed

11 files changed

+396
-14
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4590,7 +4590,27 @@ namespace {
45904590
if (auto Known = Impl.importDeclCached(decl, getVersion()))
45914591
return Known;
45924592

4593-
return importObjCMethodDecl(decl, dc, None);
4593+
ImportedName importedName;
4594+
Optional<ImportedName> correctSwiftName; // TODO: not sure if we need this.
4595+
importedName = importFullName(decl, correctSwiftName);
4596+
if (!importedName)
4597+
return nullptr;
4598+
4599+
// some ObjC method decls are imported as computed properties.
4600+
switch(importedName.getAccessorKind()) {
4601+
case ImportedAccessorKind::PropertyGetter:
4602+
if (importedName.getAsyncInfo())
4603+
return importObjCMethodAsEffectfulProp(decl, dc, importedName);
4604+
4605+
// if there is no valid async info, then fall-back to method import.
4606+
LLVM_FALLTHROUGH;
4607+
4608+
case ImportedAccessorKind::PropertySetter:
4609+
case ImportedAccessorKind::SubscriptGetter:
4610+
case ImportedAccessorKind::SubscriptSetter:
4611+
case ImportedAccessorKind::None:
4612+
return importObjCMethodDecl(decl, dc, None);
4613+
}
45944614
}
45954615

45964616
/// Check whether we have already imported a method with the given
@@ -4666,6 +4686,78 @@ namespace {
46664686
return (accessor && accessor->getAccessorKind() == accessorInfo->Kind);
46674687
}
46684688

4689+
/// Creates a fresh VarDecl with a single 'get' accessor to represent
4690+
/// an ObjC method that takes no arguments other than a completion-handler
4691+
/// (where the handler may have an NSError argument).
4692+
Decl *importObjCMethodAsEffectfulProp(const clang::ObjCMethodDecl *decl,
4693+
DeclContext *dc,
4694+
ImportedName name) {
4695+
assert(name.getAsyncInfo() && "expected to be for an effectful prop!");
4696+
4697+
if (name.getAccessorKind() != ImportedAccessorKind::PropertyGetter) {
4698+
assert(false && "unexpected accessor kind as a computed prop");
4699+
// NOTE: to handle setters, we would need to search for an existing
4700+
// VarDecl corresponding to the one we might have already created
4701+
// for the 'get' accessor, and tack this accessor onto it.
4702+
return nullptr;
4703+
}
4704+
4705+
auto importedType = Impl.importEffectfulPropertyType(decl, dc, name,
4706+
isInSystemModule(dc));
4707+
if (!importedType)
4708+
return nullptr;
4709+
4710+
auto type = importedType.getType();
4711+
const auto access = getOverridableAccessLevel(dc);
4712+
auto ident = name.getDeclName().getBaseIdentifier();
4713+
auto propDecl = Impl.createDeclWithClangNode<VarDecl>(decl, access,
4714+
/*IsStatic*/decl->isClassMethod(), VarDecl::Introducer::Var,
4715+
Impl.importSourceLoc(decl->getLocation()), ident, dc);
4716+
propDecl->setInterfaceType(type);
4717+
Impl.recordImplicitUnwrapForDecl(propDecl,
4718+
importedType.isImplicitlyUnwrapped());
4719+
4720+
////
4721+
// Build the getter
4722+
AccessorInfo info{propDecl, AccessorKind::Get};
4723+
auto *getter = cast_or_null<AccessorDecl>(
4724+
importObjCMethodDecl(decl, dc, info));
4725+
if (!getter)
4726+
return nullptr;
4727+
4728+
Impl.importAttributes(decl, getter);
4729+
4730+
////
4731+
// Combine the getter and the VarDecl into a computed property.
4732+
4733+
// NOTE: since it's an ObjC method we're turning into a Swift computed
4734+
// property, we infer that it has no ObjC 'atomic' guarantees.
4735+
auto inferredObjCPropertyAttrs =
4736+
static_cast<clang::ObjCPropertyAttribute::Kind>
4737+
( clang::ObjCPropertyAttribute::Kind::kind_readonly
4738+
| clang::ObjCPropertyAttribute::Kind::kind_nonatomic
4739+
| (decl->isInstanceMethod()
4740+
? clang::ObjCPropertyAttribute::Kind::kind_class
4741+
: clang::ObjCPropertyAttribute::Kind::kind_noattr)
4742+
);
4743+
4744+
// FIXME: Fake locations for '{' and '}'?
4745+
propDecl->setIsSetterMutating(false);
4746+
makeComputed(propDecl, getter, /*setter=*/nullptr);
4747+
addObjCAttribute(propDecl, Impl.importIdentifier(decl->getIdentifier()));
4748+
applyPropertyOwnership(propDecl, inferredObjCPropertyAttrs);
4749+
4750+
////
4751+
// Check correctness
4752+
4753+
if (getter->getParameters()->size() != 0) {
4754+
assert(false && "this should not happen!");
4755+
return nullptr;
4756+
}
4757+
4758+
return propDecl;
4759+
}
4760+
46694761
Decl *importObjCMethodDecl(const clang::ObjCMethodDecl *decl,
46704762
DeclContext *dc,
46714763
bool forceClassMethod,
@@ -4797,11 +4889,16 @@ namespace {
47974889
prop = nullptr;
47984890
}
47994891

4800-
// If we have an accessor-import request but didn't find a property,
4801-
// reject the import request.
4802-
if (accessorInfo && !prop) {
4892+
const bool nameImportIsGetter =
4893+
importedName.getAccessorKind() == ImportedAccessorKind::PropertyGetter;
4894+
4895+
const bool needAccessorDecl = prop || nameImportIsGetter;
4896+
4897+
// If we have an accessor-import request, but didn't find a property
4898+
// or it's ImportedName doesn't indicate a getter,
4899+
// then reject the import request.
4900+
if (accessorInfo && !needAccessorDecl)
48034901
return nullptr;
4804-
}
48054902

48064903
// Import the parameter list and result type.
48074904
ParameterList *bodyParams = nullptr;
@@ -4854,7 +4951,7 @@ namespace {
48544951

48554952
// If the method has a related result type that is representable
48564953
// in Swift as DynamicSelf, do so.
4857-
if (!prop && decl->hasRelatedResultType()) {
4954+
if (!needAccessorDecl && decl->hasRelatedResultType()) {
48584955
resultTy = dc->getSelfInterfaceType();
48594956
if (dc->getSelfClassDecl())
48604957
resultTy = DynamicSelfType::get(resultTy, Impl.SwiftContext);
@@ -5020,7 +5117,7 @@ namespace {
50205117
FuncDecl *setter);
50215118

50225119
/// Import the accessor and its attributes.
5023-
AccessorDecl *importAccessor(clang::ObjCMethodDecl *clangAccessor,
5120+
AccessorDecl *importAccessor(const clang::ObjCMethodDecl *clangAccessor,
50245121
AbstractStorageDecl *storage,
50255122
AccessorKind accessorKind,
50265123
DeclContext *dc);
@@ -7389,7 +7486,7 @@ SwiftDeclConverter::importSubscript(Decl *decl,
73897486
}
73907487

73917488
AccessorDecl *
7392-
SwiftDeclConverter::importAccessor(clang::ObjCMethodDecl *clangAccessor,
7489+
SwiftDeclConverter::importAccessor(const clang::ObjCMethodDecl *clangAccessor,
73937490
AbstractStorageDecl *storage,
73947491
AccessorKind accessorKind,
73957492
DeclContext *dc) {

lib/ClangImporter/ImportName.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,8 +1618,12 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
16181618
else if (parsedName.IsSetter)
16191619
result.info.accessorKind = ImportedAccessorKind::PropertySetter;
16201620

1621-
if (method && parsedName.IsFunctionName &&
1622-
result.info.accessorKind == ImportedAccessorKind::None) {
1621+
// only allow effectful property imports if through `swift_async_name`
1622+
const bool effectfulProperty = parsedName.IsGetter && nameAttr->isAsync
1623+
&& swiftCtx.LangOpts.EnableExperimentalConcurrency;
1624+
1625+
// Consider throws and async imports.
1626+
if (method && (parsedName.IsFunctionName || effectfulProperty)) {
16231627
// Get the parameters.
16241628
ArrayRef<const clang::ParmVarDecl *> params{method->param_begin(),
16251629
method->param_end()};

lib/ClangImporter/ImportType.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,6 +2203,49 @@ static Type decomposeCompletionHandlerType(
22032203
}
22042204
}
22052205

2206+
ImportedType ClangImporter::Implementation::importEffectfulPropertyType(
2207+
const clang::ObjCMethodDecl *decl,
2208+
DeclContext *dc,
2209+
importer::ImportedName name,
2210+
bool isFromSystemModule) {
2211+
// here we expect a method that is being imported as an effectful property.
2212+
// thus, we currently require async info.
2213+
if (!name.getAsyncInfo())
2214+
return ImportedType();
2215+
2216+
// a variadic method doesn't make sense here
2217+
if (decl->isVariadic())
2218+
return ImportedType();
2219+
2220+
// Our strategy here is to determine what the return type of the method would
2221+
// be, had we imported it as a method.
2222+
2223+
Optional<ForeignAsyncConvention> asyncConvention;
2224+
Optional<ForeignErrorConvention> errorConvention;
2225+
2226+
const auto kind = SpecialMethodKind::Regular;
2227+
2228+
// Import the parameter list and result type.
2229+
ParameterList *bodyParams = nullptr;
2230+
ImportedType importedType;
2231+
2232+
auto methodReturnType = importMethodParamsAndReturnType(
2233+
dc, decl, decl->parameters(), false,
2234+
isFromSystemModule, &bodyParams, name,
2235+
asyncConvention, errorConvention, kind);
2236+
2237+
// getter mustn't have any parameters!
2238+
if (bodyParams->size() != 0) {
2239+
return ImportedType();
2240+
}
2241+
2242+
// We expect that the method, after import, will have only an async convention
2243+
if (!asyncConvention || errorConvention)
2244+
return ImportedType();
2245+
2246+
return methodReturnType;
2247+
}
2248+
22062249
ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
22072250
const DeclContext *dc, const clang::ObjCMethodDecl *clangDecl,
22082251
ArrayRef<const clang::ParmVarDecl *> params, bool isVariadic,

lib/ClangImporter/ImporterImpl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,13 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
11991199
ImportedType importPropertyType(const clang::ObjCPropertyDecl *clangDecl,
12001200
bool isFromSystemModule);
12011201

1202+
/// Determines what the type of an effectful, computed read-only property
1203+
/// would be, if the given method were imported as such a property.
1204+
ImportedType importEffectfulPropertyType(const clang::ObjCMethodDecl *decl,
1205+
DeclContext *dc,
1206+
importer::ImportedName name,
1207+
bool isFromSystemModule);
1208+
12021209
/// Attempt to infer a default argument for a parameter with the
12031210
/// given Clang \c type, \c baseName, and optionality.
12041211
static DefaultArgumentKind

test/Concurrency/Runtime/Inputs/objc_async.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@
77
- (void)butt:(NSInteger)x completionHandler:(void (^ _Nonnull)(NSInteger))handler;
88

99
@end
10+
11+
@interface Farm: NSObject
12+
13+
-(void)getDogWithCompletion:(void (^ _Nonnull)(NSInteger))completionHandler
14+
__attribute__((swift_async_name("getter:doggo()")));
15+
16+
-(void)obtainCat:(void (^ _Nonnull)(NSInteger, NSError* _Nullable))completionHandler
17+
__attribute__((swift_async_name("getter:catto()")));
18+
19+
@end

test/Concurrency/Runtime/Inputs/objc_async.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,17 @@ - (void)butt:(NSInteger)x completionHandler:(void (^)(NSInteger))handler {
1313
}
1414

1515
@end
16+
17+
@implementation Farm
18+
19+
-(void)getDogWithCompletion:(void (^ _Nonnull)(NSInteger))completionHandler {
20+
printf("getting dog\n");
21+
completionHandler(123);
22+
}
23+
24+
-(void)obtainCat:(void (^ _Nonnull)(NSInteger, NSError* _Nullable))completionHandler {
25+
printf("obtaining cat has failed!\n");
26+
completionHandler(nil, [NSError errorWithDomain:@"obtainCat" code:456 userInfo:nil]);
27+
}
28+
29+
@end

test/Concurrency/Runtime/objc_async.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,35 @@
1010
// rdar://76038845
1111
// UNSUPPORTED: use_os_stdlib
1212

13-
@main struct Main {
14-
static func main() async {
13+
func buttTest() async {
1514
let butt = Butt()
1615
let result = await butt.butt(1738)
1716
print("finishing \(result)")
17+
}
18+
19+
func farmTest() async {
20+
let farm = Farm()
21+
let dogNumber = await farm.doggo
22+
print("dog number = \(dogNumber)")
23+
do {
24+
let _ = try await farm.catto
25+
} catch {
26+
print("caught exception")
1827
}
1928
}
2029

21-
// CHECK: starting 1738
22-
// CHECK-NEXT: finishing 679
30+
@main struct Main {
31+
static func main() async {
32+
// CHECK: starting 1738
33+
// CHECK-NEXT: finishing 679
34+
await buttTest()
35+
36+
// CHECK-NEXT: getting dog
37+
// CHECK-NEXT: dog number = 123
38+
// CHECK-NEXT: obtaining cat has failed!
39+
// CHECK-NEXT: caught exception
40+
await farmTest()
41+
}
42+
}
43+
44+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -print-module -source-filename %s -module-to-print=EffectfulProperties -function-definitions=false -enable-experimental-concurrency > %t/EffectfulProperties.printed.txt
4+
// RUN: %FileCheck -input-file %t/EffectfulProperties.printed.txt %s
5+
6+
// REQUIRES: objc_interop
7+
// REQUIRES: concurrency
8+
9+
// CHECK-LABEL: class EffProps : NSObject {
10+
// CHECK: func getDogWithCompletion(_ completionHandler: @escaping (NSObject) -> Void)
11+
// CHECK: var doggo: NSObject { get async }
12+
13+
// CHECK: func obtainCat(_ completionHandler: @escaping (NSObject?, Error?) -> Void)
14+
// CHECK-NEXT: var catto: NSObject? { get async throws }
15+
16+
// CHECK: func checkAvailability(completionHandler: @escaping (Bool) -> Void)
17+
// CHECK-NEXT: var available: Bool { get async }
18+
// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "checkAvailability(completionHandler:)")
19+
// CHECK-NEXT: func checkAvailabilityWithCompletionHandler(_ completionHandler: @escaping (Bool) -> Void)
20+
21+
// CHECK: func returnNothing(completion completionHandler: @escaping () -> Void)
22+
// CHECK-NEXT: var touch: Void { get async }
23+
// CHECK-NEXT: @available(swift, obsoleted: 3, renamed: "returnNothing(completion:)")
24+
// CHECK-NEXT: func returnNothingWithCompletion(_ completionHandler: @escaping () -> Void)
25+
26+
// CHECK: func nullableHandler(_ completion: ((String) -> Void)? = nil)
27+
// CHECK-NEXT: var fromNullableHandler: String { get async }
28+
29+
// CHECK: func getMainDog(_ completion: @escaping @MainActor (String) -> Void)
30+
// CHECK-NEXT: var mainDogProp: String { get async }
31+
32+
// CHECK: @completionHandlerAsync("regularMainDog()", completionHandleIndex: 0)
33+
// CHECK-NEXT: func regularMainDog(_ completion: @escaping @MainActor (String) -> Void)
34+
// CHECK-NEXT: func regularMainDog() async -> String
35+
// CHECK: }
36+
37+
// CHECK-LABEL: class NotEffProps : NSObject {
38+
// CHECK-NOT: var
39+
// CHECK: func EffPropGetDogWithCompletion(_ s: OpaquePointer, _ myBlock: @escaping (NSObject) -> Void) -> Double

0 commit comments

Comments
 (0)