Skip to content

Commit 915afc3

Browse files
committed
[Clang importer] Implement support for importing _Nullable_result.
`_Nullable_result` indicates that a parameter of a completion handler should be imported as optional when the completion handler can fail by throwing an error. Implements rdar://70108088.
1 parent b80e4bf commit 915afc3

File tree

7 files changed

+90
-19
lines changed

7 files changed

+90
-19
lines changed

lib/ClangImporter/ClangAdapter.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,12 +607,18 @@ bool importer::hasNativeSwiftDecl(const clang::Decl *decl) {
607607

608608
/// Translate the "nullability" notion from API notes into an optional type
609609
/// kind.
610-
OptionalTypeKind importer::translateNullability(clang::NullabilityKind kind) {
610+
OptionalTypeKind importer::translateNullability(
611+
clang::NullabilityKind kind, bool stripNonResultOptionality) {
612+
if (stripNonResultOptionality &&
613+
kind != clang::NullabilityKind::NullableResult)
614+
return OptionalTypeKind::OTK_None;
615+
611616
switch (kind) {
612617
case clang::NullabilityKind::NonNull:
613618
return OptionalTypeKind::OTK_None;
614619

615620
case clang::NullabilityKind::Nullable:
621+
case clang::NullabilityKind::NullableResult:
616622
return OptionalTypeKind::OTK_Optional;
617623

618624
case clang::NullabilityKind::Unspecified:

lib/ClangImporter/ClangAdapter.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ bool isNSString(clang::QualType);
137137
bool hasNativeSwiftDecl(const clang::Decl *decl);
138138

139139
/// Translation API nullability from an API note into an optional kind.
140-
OptionalTypeKind translateNullability(clang::NullabilityKind kind);
140+
///
141+
/// \param stripNonResultOptionality Whether strip optionality from
142+
/// \c _Nullable but not \c _Nullable_result.
143+
OptionalTypeKind translateNullability(
144+
clang::NullabilityKind kind, bool stripNonResultOptionality = false);
141145

142146
/// Determine whether the given method is a required initializer
143147
/// of the given class.

lib/ClangImporter/ImportType.cpp

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,18 @@ namespace {
195195
ClangImporter::Implementation &Impl;
196196
bool AllowNSUIntegerAsInt;
197197
Bridgeability Bridging;
198+
const clang::FunctionType *CompletionHandlerType;
199+
Optional<unsigned> CompletionHandlerErrorParamIndex;
198200

199201
public:
200202
SwiftTypeConverter(ClangImporter::Implementation &impl,
201203
bool allowNSUIntegerAsInt,
202-
Bridgeability bridging)
204+
Bridgeability bridging,
205+
const clang::FunctionType *completionHandlerType,
206+
Optional<unsigned> completionHandlerErrorParamIndex)
203207
: Impl(impl), AllowNSUIntegerAsInt(allowNSUIntegerAsInt),
204-
Bridging(bridging) {}
208+
Bridging(bridging), CompletionHandlerType(completionHandlerType),
209+
CompletionHandlerErrorParamIndex(completionHandlerErrorParamIndex) {}
205210

206211
using TypeVisitor::Visit;
207212
ImportResult Visit(clang::QualType type) {
@@ -612,8 +617,17 @@ namespace {
612617
for (auto param = type->param_type_begin(),
613618
paramEnd = type->param_type_end();
614619
param != paramEnd; ++param) {
620+
// Determine whether we have a result parameter of a completion
621+
// handler that can also express a thrown error.
622+
ImportTypeKind paramImportKind = ImportTypeKind::Parameter;
623+
unsigned paramIdx = param - type->param_type_begin();
624+
if (type == CompletionHandlerType &&
625+
paramIdx != CompletionHandlerErrorParamIndex) {
626+
paramImportKind = ImportTypeKind::CompletionHandlerResultParameter;
627+
}
628+
615629
auto swiftParamTy = Impl.importTypeIgnoreIUO(
616-
*param, ImportTypeKind::Parameter, AllowNSUIntegerAsInt, Bridging,
630+
*param, paramImportKind, AllowNSUIntegerAsInt, Bridging,
617631
OTK_Optional);
618632
if (!swiftParamTy)
619633
return Type();
@@ -1191,6 +1205,7 @@ static bool canBridgeTypes(ImportTypeKind importKind) {
11911205
case ImportTypeKind::Result:
11921206
case ImportTypeKind::AuditedResult:
11931207
case ImportTypeKind::Parameter:
1208+
case ImportTypeKind::CompletionHandlerResultParameter:
11941209
case ImportTypeKind::CFRetainedOutParameter:
11951210
case ImportTypeKind::CFUnretainedOutParameter:
11961211
case ImportTypeKind::Property:
@@ -1218,6 +1233,7 @@ static bool isCFAudited(ImportTypeKind importKind) {
12181233
case ImportTypeKind::AuditedVariable:
12191234
case ImportTypeKind::AuditedResult:
12201235
case ImportTypeKind::Parameter:
1236+
case ImportTypeKind::CompletionHandlerResultParameter:
12211237
case ImportTypeKind::CFRetainedOutParameter:
12221238
case ImportTypeKind::CFUnretainedOutParameter:
12231239
case ImportTypeKind::Property:
@@ -1520,7 +1536,8 @@ static ImportedType adjustTypeForConcreteImport(
15201536
ImportedType ClangImporter::Implementation::importType(
15211537
clang::QualType type, ImportTypeKind importKind, bool allowNSUIntegerAsInt,
15221538
Bridgeability bridging, OptionalTypeKind optionality,
1523-
bool resugarNSErrorPointer) {
1539+
bool resugarNSErrorPointer,
1540+
Optional<unsigned> completionHandlerErrorParamIndex) {
15241541
if (type.isNull())
15251542
return {Type(), false};
15261543

@@ -1555,11 +1572,26 @@ ImportedType ClangImporter::Implementation::importType(
15551572
// If nullability is provided as part of the type, that overrides
15561573
// optionality provided externally.
15571574
if (auto nullability = type->getNullability(clangContext)) {
1558-
optionality = translateNullability(*nullability);
1575+
bool stripNonResultOptionality =
1576+
importKind == ImportTypeKind::CompletionHandlerResultParameter;
1577+
1578+
optionality = translateNullability(*nullability, stripNonResultOptionality);
1579+
}
1580+
1581+
// If this is a completion handler parameter, record the function type whose
1582+
// parameters will act as the results of the completion handler.
1583+
const clang::FunctionType *completionHandlerType = nullptr;
1584+
if (completionHandlerErrorParamIndex) {
1585+
if (auto blockPtrType = type->getAs<clang::BlockPointerType>()) {
1586+
completionHandlerType =
1587+
blockPtrType->getPointeeType()->castAs<clang::FunctionType>();
1588+
}
15591589
}
15601590

15611591
// Perform abstract conversion, ignoring how the type is actually used.
1562-
SwiftTypeConverter converter(*this, allowNSUIntegerAsInt, bridging);
1592+
SwiftTypeConverter converter(
1593+
*this, allowNSUIntegerAsInt, bridging,
1594+
completionHandlerType, completionHandlerErrorParamIndex);
15631595
auto importResult = converter.Visit(type);
15641596

15651597
// Now fix up the type based on how we're concretely using it.
@@ -2085,13 +2117,7 @@ static Type decomposeCompletionHandlerType(
20852117
paramIdx == *info.completionHandlerErrorParamIndex())
20862118
continue;
20872119

2088-
// If there is an error parameter, remove nullability.
2089-
Type paramType = param.getPlainType();
2090-
// TODO: Clang should gain a nullability form that overrides this.
2091-
if (info.completionHandlerErrorParamIndex())
2092-
paramType = paramType->lookThroughAllOptionalTypes();
2093-
2094-
resultTypeElts.push_back(paramType);
2120+
resultTypeElts.push_back(param.getPlainType());
20952121
}
20962122

20972123
switch (resultTypeElts.size()) {
@@ -2266,6 +2292,7 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
22662292
}
22672293

22682294
// Special case for NSDictionary's subscript.
2295+
ImportTypeKind importKind = ImportTypeKind::Parameter;
22692296
Type swiftParamTy;
22702297
bool paramIsIUO;
22712298
if (kind == SpecialMethodKind::NSDictionarySubscriptGetter &&
@@ -2276,12 +2303,19 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
22762303

22772304
paramIsIUO = optionalityOfParam == OTK_ImplicitlyUnwrappedOptional;
22782305
} else {
2279-
ImportTypeKind importKind = ImportTypeKind::Parameter;
22802306
if (param->hasAttr<clang::CFReturnsRetainedAttr>())
22812307
importKind = ImportTypeKind::CFRetainedOutParameter;
22822308
else if (param->hasAttr<clang::CFReturnsNotRetainedAttr>())
22832309
importKind = ImportTypeKind::CFUnretainedOutParameter;
22842310

2311+
// Figure out if this is a completion handler parameter whose error
2312+
// parameter is used to indicate throwing.
2313+
Optional<unsigned> completionHandlerErrorParamIndex;
2314+
if (paramIsCompletionHandler) {
2315+
completionHandlerErrorParamIndex =
2316+
asyncInfo->completionHandlerErrorParamIndex();
2317+
}
2318+
22852319
// If this is the throws error parameter, we don't need to convert any
22862320
// NSError** arguments to the sugared NSErrorPointer typealias form,
22872321
// because all that is done with it is retrieving the canonical
@@ -2293,7 +2327,8 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
22932327
auto importedParamType =
22942328
importType(paramTy, importKind, allowNSUIntegerAsIntInParam,
22952329
Bridgeability::Full, optionalityOfParam,
2296-
/*resugarNSErrorPointer=*/!paramIsError);
2330+
/*resugarNSErrorPointer=*/!paramIsError,
2331+
completionHandlerErrorParamIndex);
22972332
paramIsIUO = importedParamType.isImplicitlyUnwrapped();
22982333
swiftParamTy = importedParamType.getType();
22992334
}
@@ -2321,7 +2356,14 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
23212356
if (Type replacedSwiftResultTy =
23222357
decomposeCompletionHandlerType(swiftParamTy, *asyncInfo)) {
23232358
swiftResultTy = replacedSwiftResultTy;
2324-
completionHandlerType = swiftParamTy->getCanonicalType();
2359+
2360+
// Import the original completion handler type without adjustments.
2361+
Type origSwiftParamTy = importType(
2362+
paramTy, importKind, allowNSUIntegerAsIntInParam,
2363+
Bridgeability::Full, optionalityOfParam,
2364+
/*resugarNSErrorPointer=*/!paramIsError, None).getType();
2365+
completionHandlerType = mapGenericArgs(origDC, dc, origSwiftParamTy)
2366+
->getCanonicalType();
23252367
continue;
23262368
}
23272369

lib/ClangImporter/ImporterImpl.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ enum class ImportTypeKind {
142142
/// Parameters are always considered CF-audited.
143143
Parameter,
144144

145+
/// Import the type of a parameter to a completion handler that can indicate
146+
/// a thrown error.
147+
///
148+
/// Special handling:
149+
/// * _Nullable_result is treated as _Nonnull rather than _Nullable_result.
150+
CompletionHandlerResultParameter,
151+
145152
/// Import the type of a parameter declared with
146153
/// \c CF_RETURNS_RETAINED.
147154
///
@@ -1036,7 +1043,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
10361043
importType(clang::QualType type, ImportTypeKind kind,
10371044
bool allowNSUIntegerAsInt, Bridgeability topLevelBridgeability,
10381045
OptionalTypeKind optional = OTK_ImplicitlyUnwrappedOptional,
1039-
bool resugarNSErrorPointer = true);
1046+
bool resugarNSErrorPointer = true,
1047+
Optional<unsigned> completionHandlerErrorParamIndex = None);
10401048

10411049
/// Import the given Clang type into Swift.
10421050
///

test/ClangImporter/objc_async.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ func testSlowServer(slowServer: SlowServer) async throws {
1010
let _: Bool = await slowServer.checkAvailability()
1111
let _: String = await try slowServer.findAnswer()
1212
let _: String = await try slowServer.findAnswerFailingly()
13+
14+
let (aOpt, b) = await try slowServer.findQAndA()
15+
if let a = aOpt { // make sure aOpt is optional
16+
print(a)
17+
}
18+
let _: String = b // make sure b is non-optional
19+
20+
let _: String = await try slowServer.findAnswer()
21+
1322
let _: Void = await slowServer.doSomethingFun("jump")
1423
let _: (Int) -> Void = slowServer.completionHandler
1524

test/IDE/print_clang_objc_async.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// CHECK-DAG: func findAnswer() async throws -> String
2121
// CHECK-DAG: func findAnswerFailingly(completionHandler handler: @escaping (String?, Error?) -> Void) throws
2222
// CHECK-DAG: func findAnswerFailingly() async throws -> String
23+
// CHECK-DAG: func findQAndA() async throws -> (String?, String)
2324
// CHECK-DAG: func doSomethingFun(_ operation: String) async
2425
// CHECK: {{^[}]$}}
2526

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
-(void)replyingOperation:(NSString *)operation replyTo:(void (^)(NSString *))block;
1818
-(void)findAnswerAsynchronously:(void (^)(NSString *_Nullable, NSError * _Nullable))handler __attribute__((swift_name("findAnswer(completionHandler:)")));
1919
-(BOOL)findAnswerFailinglyWithError:(NSError * _Nullable * _Nullable)error completion:(void (^)(NSString *_Nullable, NSError * _Nullable))handler __attribute__((swift_name("findAnswerFailingly(completionHandler:)")));
20+
-(void)findQAndAWithCompletionHandler:(void (^)(NSString *_Nullable_result, NSString *_Nullable answer, NSError * _Nullable))handler;
2021
-(void)doSomethingFun:(NSString *)operation then:(void (^)(void))completionHandler;
2122
-(void)getFortuneAsynchronouslyWithCompletionHandler:(void (^)(NSString *_Nullable, NSError * _Nullable))handler;
2223
-(void)getMagicNumberAsynchronouslyWithSeed:(NSInteger)seed completionHandler:(void (^)(NSInteger, NSError * _Nullable))handler;

0 commit comments

Comments
 (0)