Use getDartType rather than getFfiDartType in ObjC block codegen (#632)
* Blocks returning proper user facing types
* More tests and refactors
* fmt
* Fix test
* Fix vararg test
* Partial fix for block ref counts
* More block ref counting fixes
* fmt
* Daco's comments
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9d4b88..2d90855 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
- Fix ObjC methods returning instancetype having the wrong type in sublasses.
- When generating typedefs for `Pointer<NativeFunction<Function>>`, also
generate a typedef for the `Function`.
+- Use Dart wrapper types in args and returns of ObjCBlocks.
- Bump min SDK version to 3.2.0-114.0.dev.
# 9.0.1
diff --git a/lib/src/code_generator/func_type.dart b/lib/src/code_generator/func_type.dart
index e1d0793..1e734bc 100644
--- a/lib/src/code_generator/func_type.dart
+++ b/lib/src/code_generator/func_type.dart
@@ -23,81 +23,66 @@
this.varArgParameters = const [],
});
- String _getCacheKeyString(
- bool writeArgumentNames, String Function(Type) typeToString) {
- final sb = StringBuffer();
+ String _getTypeImpl(
+ bool writeArgumentNames, String Function(Type) typeToString,
+ {String? varArgWrapper}) {
+ final params = varArgWrapper != null ? parameters : dartTypeParameters;
+ String? varArgPack;
+ if (varArgWrapper != null && varArgParameters.isNotEmpty) {
+ final varArgPackBuf = StringBuffer();
+ varArgPackBuf.write("$varArgWrapper<(");
+ varArgPackBuf.write((varArgParameters).map<String>((p) {
+ return '${typeToString(p.type)} ${writeArgumentNames ? p.name : ""}';
+ }).join(', '));
+ varArgPackBuf.write(",)>");
+ varArgPack = varArgPackBuf.toString();
+ }
// Write return Type.
+ final sb = StringBuffer();
sb.write(typeToString(returnType));
// Write Function.
sb.write(' Function(');
- sb.write(parameters.map<String>((p) {
- return '${typeToString(p.type)} ${writeArgumentNames ? p.name : ""}';
- }).join(', '));
+ sb.write([
+ ...params.map<String>((p) {
+ return '${typeToString(p.type)} ${writeArgumentNames ? p.name : ""}';
+ }),
+ if (varArgPack != null) varArgPack,
+ ].join(', '));
sb.write(')');
return sb.toString();
}
@override
- String getCType(Writer w, {bool writeArgumentNames = true}) {
- final sb = StringBuffer();
-
- // Write return Type.
- sb.write(returnType.getCType(w));
-
- // Write Function.
- sb.write(' Function(');
- sb.write((parameters).map<String>((p) {
- return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}';
- }).join(', '));
- if (varArgParameters.isNotEmpty) {
- sb.write(", ${w.ffiLibraryPrefix}.VarArgs<(");
- sb.write((varArgParameters).map<String>((p) {
- return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}';
- }).join(', '));
- sb.write(",)>");
- }
- sb.write(')');
-
- return sb.toString();
- }
+ String getCType(Writer w, {bool writeArgumentNames = true}) =>
+ _getTypeImpl(writeArgumentNames, (Type t) => t.getCType(w),
+ varArgWrapper: '${w.ffiLibraryPrefix}.VarArgs');
@override
- String getFfiDartType(Writer w, {bool writeArgumentNames = true}) {
- final sb = StringBuffer();
+ String getFfiDartType(Writer w, {bool writeArgumentNames = true}) =>
+ _getTypeImpl(writeArgumentNames, (Type t) => t.getFfiDartType(w));
- // Write return Type.
- sb.write(returnType.getFfiDartType(w));
-
- // Write Function.
- sb.write(' Function(');
- sb.write(dartTypeParameters.map<String>((p) {
- return '${p.type.getFfiDartType(w)} ${writeArgumentNames ? p.name : ""}';
- }).join(', '));
- sb.write(')');
-
- return sb.toString();
- }
+ @override
+ String getDartType(Writer w, {bool writeArgumentNames = true}) =>
+ _getTypeImpl(writeArgumentNames, (Type t) => t.getDartType(w));
@override
bool get sameFfiDartAndCType =>
returnType.sameFfiDartAndCType &&
- parameters.every((p) => p.type.sameFfiDartAndCType) &&
- varArgParameters.every((p) => p.type.sameFfiDartAndCType);
+ dartTypeParameters.every((p) => p.type.sameFfiDartAndCType);
@override
bool get sameDartAndCType =>
returnType.sameDartAndCType &&
- parameters.every((p) => p.type.sameDartAndCType) &&
- varArgParameters.every((p) => p.type.sameDartAndCType);
+ dartTypeParameters.every((p) => p.type.sameDartAndCType);
@override
- String toString() => _getCacheKeyString(false, (Type t) => t.toString());
+ String toString() => _getTypeImpl(false, (Type t) => t.toString());
@override
- String cacheKey() => _getCacheKeyString(false, (Type t) => t.cacheKey());
+ String cacheKey() => _getTypeImpl(false, (Type t) => t.cacheKey());
@override
void addDependencies(Set<Binding> dependencies) {
diff --git a/lib/src/code_generator/objc_block.dart b/lib/src/code_generator/objc_block.dart
index eb891fc..b61efc7 100644
--- a/lib/src/code_generator/objc_block.dart
+++ b/lib/src/code_generator/objc_block.dart
@@ -73,31 +73,36 @@
final trampFuncType = FunctionType(
returnType: returnType,
parameters: [Parameter(type: blockPtr, name: 'block'), ...params]);
- final natTrampFnType = NativeFunc(trampFuncType);
+ final trampFuncCType = trampFuncType.getCType(w, writeArgumentNames: false);
+ final trampFuncFfiDartType =
+ trampFuncType.getFfiDartType(w, writeArgumentNames: false);
+ final natTrampFnType = NativeFunc(trampFuncType).getCType(w);
final nativeCallableType =
- '${w.ffiLibraryPrefix}.NativeCallable<${trampFuncType.getCType(w)}>';
+ '${w.ffiLibraryPrefix}.NativeCallable<$trampFuncCType>';
+ final funcDartType = funcType.getDartType(w, writeArgumentNames: false);
+ final funcFfiDartType =
+ funcType.getFfiDartType(w, writeArgumentNames: false);
+ final returnFfiDartType = returnType.getFfiDartType(w);
+ final blockCType = blockPtr.getCType(w);
+
+ final paramsNameOnly = params.map((p) => p.name).join(', ');
+ final paramsFfiDartType =
+ params.map((p) => '${p.type.getFfiDartType(w)} ${p.name}').join(', ');
+ final paramsDartType =
+ params.map((p) => '${p.type.getDartType(w)} ${p.name}').join(', ');
// Write the function pointer based trampoline function.
- s.write(returnType.getFfiDartType(w));
- s.write(' $funcPtrTrampoline(${blockPtr.getCType(w)} block');
- for (int i = 0; i < params.length; ++i) {
- s.write(', ${params[i].type.getFfiDartType(w)} ${params[i].name}');
- }
- s.write(') {\n');
- s.write(' ${isVoid ? '' : 'return '}block.ref.target.cast<'
- '${natFnType.getFfiDartType(w)}>().asFunction<'
- '${funcType.getFfiDartType(w)}>()(');
- for (int i = 0; i < params.length; ++i) {
- s.write('${i == 0 ? '' : ', '}${params[i].name}');
- }
- s.write(');\n');
- s.write('}\n');
+ s.write('''
+$returnFfiDartType $funcPtrTrampoline($blockCType block, $paramsFfiDartType) =>
+ block.ref.target.cast<${natFnType.getFfiDartType(w)}>()
+ .asFunction<$funcFfiDartType>()($paramsNameOnly);
+''');
// Write the closure registry function.
s.write('''
-final $closureRegistry = <int, Function>{};
+final $closureRegistry = <int, $funcFfiDartType>{};
int $closureRegistryIndex = 0;
-$voidPtr $registerClosure(Function fn) {
+$voidPtr $registerClosure($funcFfiDartType fn) {
final id = ++$closureRegistryIndex;
$closureRegistry[id] = fn;
return $voidPtr.fromAddress(id);
@@ -105,33 +110,30 @@
''');
// Write the closure based trampoline function.
- s.write(returnType.getFfiDartType(w));
- s.write(' $closureTrampoline(${blockPtr.getCType(w)} block');
- for (int i = 0; i < params.length; ++i) {
- s.write(', ${params[i].type.getFfiDartType(w)} ${params[i].name}');
- }
- s.write(') {\n');
- s.write(' ${isVoid ? '' : 'return '}');
- s.write('($closureRegistry[block.ref.target.address]');
- s.write(' as ${returnType.getFfiDartType(w)} Function(');
- for (int i = 0; i < params.length; ++i) {
- s.write('${i == 0 ? '' : ', '}${params[i].type.getFfiDartType(w)}');
- }
- s.write('))');
- s.write('(');
- for (int i = 0; i < params.length; ++i) {
- s.write('${i == 0 ? '' : ', '}${params[i].name}');
- }
- s.write(');\n');
- s.write('}\n');
+ s.write('''
+$returnFfiDartType $closureTrampoline($blockCType block, $paramsFfiDartType) =>
+ $closureRegistry[block.ref.target.address]!($paramsNameOnly);
+''');
+
+ // Snippet that converts a Dart typed closure to FfiDart type. This snippet
+ // is used below. Note that the closure being converted is called `fn`.
+ final convertedFnArgs = params
+ .map((p) => p.type
+ .convertFfiDartTypeToDartType(w, p.name, 'lib', objCRetain: true))
+ .join(', ');
+ final convFnInvocation = returnType.convertDartTypeToFfiDartType(
+ w, 'fn($convertedFnArgs)',
+ objCRetain: true);
+ final convFn = '($paramsFfiDartType) => $convFnInvocation';
// Write the wrapper class.
final defaultValue = returnType.getDefaultValue(w, '_lib');
final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue';
s.write('''
class $name extends _ObjCBlockBase {
- $name._(${blockPtr.getCType(w)} id, ${w.className} lib) :
- super._(id, lib, retain: false, release: true);
+ $name._($blockCType id, ${w.className} lib,
+ {bool retain = false, bool release = true}) :
+ super._(id, lib, retain: retain, release: release);
/// Creates a block from a C function pointer.
///
@@ -141,7 +143,7 @@
$name.fromFunctionPointer(${w.className} lib, $natFnPtr ptr) :
this._(lib.${builtInFunctions.newBlock.name}(
_cFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction<
- ${trampFuncType.getCType(w)}>($funcPtrTrampoline
+ $trampFuncCType>($funcPtrTrampoline
$exceptionalReturn).cast(), ptr.cast()), lib);
static $voidPtr? _cFuncTrampoline;
@@ -150,11 +152,11 @@
/// This block must be invoked by native code running on the same thread as
/// the isolate that registered it. Invoking the block on the wrong thread
/// will result in a crash.
- $name.fromFunction(${w.className} lib, ${funcType.getFfiDartType(w)} fn) :
+ $name.fromFunction(${w.className} lib, $funcDartType fn) :
this._(lib.${builtInFunctions.newBlock.name}(
_dartFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction<
- ${trampFuncType.getCType(w)}>($closureTrampoline
- $exceptionalReturn).cast(), $registerClosure(fn)), lib);
+ $trampFuncCType>($closureTrampoline
+ $exceptionalReturn).cast(), $registerClosure($convFn)), lib);
static $voidPtr? _dartFuncTrampoline;
''');
@@ -171,31 +173,30 @@
///
/// Note that unlike the default behavior of NativeCallable.listener, listener
/// blocks do not keep the isolate alive.
- $name.listener(${w.className} lib, ${funcType.getFfiDartType(w)} fn) :
+ $name.listener(${w.className} lib, $funcDartType fn) :
this._(lib.${builtInFunctions.newBlock.name}(
- (_dartFuncListenerTrampoline ??= $nativeCallableType.listener($closureTrampoline
- $exceptionalReturn)..keepIsolateAlive = false).nativeFunction.cast(),
- $registerClosure(fn)), lib);
+ (_dartFuncListenerTrampoline ??= $nativeCallableType.listener(
+ $closureTrampoline $exceptionalReturn)..keepIsolateAlive =
+ false).nativeFunction.cast(),
+ $registerClosure($convFn)), lib);
static $nativeCallableType? _dartFuncListenerTrampoline;
''');
}
// Call method.
- s.write(' ${returnType.getFfiDartType(w)} call(');
- for (int i = 0; i < params.length; ++i) {
- s.write('${i == 0 ? '' : ', '}${params[i].type.getFfiDartType(w)}');
- s.write(' ${params[i].name}');
- }
- s.write(''') {
- ${isVoid ? '' : 'return '}_id.ref.invoke.cast<
- ${natTrampFnType.getCType(w)}>().asFunction<
- ${trampFuncType.getFfiDartType(w)}>()(_id''');
- for (int i = 0; i < params.length; ++i) {
- s.write(', ${params[i].name}');
- }
- s.write(''');
- }''');
+ s.write(' ${returnType.getDartType(w)} call($paramsDartType) =>');
+ final callMethodArgs = params
+ .map((p) =>
+ p.type.convertDartTypeToFfiDartType(w, p.name, objCRetain: false))
+ .join(', ');
+ final callMethodInvocation = '''
+_id.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()(
+ _id, $callMethodArgs)''';
+ s.write(returnType.convertFfiDartTypeToDartType(
+ w, callMethodInvocation, '_lib',
+ objCRetain: false));
+ s.write(';\n');
s.write('}\n');
return BindingString(
@@ -228,5 +229,23 @@
bool get sameDartAndCType => false;
@override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ ObjCInterface.generateGetId(value, objCRetain);
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ ObjCInterface.generateConstructor(name, value, library, objCRetain);
+
+ @override
String toString() => '($returnType (^)(${argTypes.join(', ')}))';
}
diff --git a/lib/src/code_generator/objc_built_in_functions.dart b/lib/src/code_generator/objc_built_in_functions.dart
index b772861..60c998b 100644
--- a/lib/src/code_generator/objc_built_in_functions.dart
+++ b/lib/src/code_generator/objc_built_in_functions.dart
@@ -239,6 +239,11 @@
/// Return a pointer to this object.
$idType get pointer => _id;
+
+ $idType _retainAndReturnId() {
+ _lib.$retain(_id.cast());
+ return _id;
+ }
}
''');
}
diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart
index e4cf260..2ed1f10 100644
--- a/lib/src/code_generator/objc_interface.dart
+++ b/lib/src/code_generator/objc_interface.dart
@@ -207,12 +207,19 @@
s.write(isStatic ? '_lib.${_classObject.name}' : '_id');
s.write(', _lib.${m.selObject!.name}');
for (final p in m.params) {
- s.write(', ${_doArgConversion(p)}');
+ final convertedParam =
+ p.type.convertDartTypeToFfiDartType(w, p.name, objCRetain: false);
+ s.write(', $convertedParam');
}
s.write(');\n');
if (convertReturn) {
- final result = _doReturnConversion(
- returnType, '_ret', name, '_lib', m.isOwnedReturn);
+ final result = returnType.convertFfiDartTypeToDartType(
+ w,
+ '_ret',
+ '_lib',
+ objCRetain: !m.isOwnedReturn,
+ objCEnclosingClass: name,
+ );
s.write(' return $result;');
}
@@ -394,6 +401,37 @@
@override
bool get sameDartAndCType => false;
+ @override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ ObjCInterface.generateGetId(value, objCRetain);
+
+ static String generateGetId(String value, bool objCRetain) =>
+ objCRetain ? '$value._retainAndReturnId()' : '$value._id';
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ ObjCInterface.generateConstructor(name, value, library, objCRetain);
+
+ static String generateConstructor(
+ String className,
+ String value,
+ String library,
+ bool objCRetain,
+ ) {
+ final ownershipFlags = 'retain: $objCRetain, release: true';
+ return '$className._($value, $library, $ownershipFlags)';
+ }
+
// Utils for converting between the internal types passed to native code, and
// the external types visible to the user. For example, ObjCInterfaces are
// passed to native as Pointer<ObjCObject>, but the user sees the Dart wrapper
@@ -413,44 +451,6 @@
}
return type.getDartType(w);
}
-
- String _doArgConversion(ObjCMethodParam arg) {
- final baseType = arg.type.typealiasType;
- if (baseType is ObjCNullable) {
- return '${arg.name}?._id ?? ffi.nullptr';
- } else if (arg.type is ObjCInstanceType ||
- baseType is ObjCInterface ||
- baseType is ObjCObjectPointer ||
- baseType is ObjCBlock) {
- return '${arg.name}._id';
- }
- return arg.name;
- }
-
- String _doReturnConversion(Type type, String value, String enclosingClass,
- String library, bool isOwnedReturn) {
- var prefix = '';
- var baseType = type.typealiasType;
- if (baseType is ObjCNullable) {
- prefix = '$value.address == 0 ? null : ';
- type = baseType.child;
- baseType = type.typealiasType;
- }
- final ownerFlags = 'retain: ${!isOwnedReturn}, release: true';
- if (type is ObjCInstanceType) {
- return '$prefix$enclosingClass._($value, $library, $ownerFlags)';
- }
- if (baseType is ObjCInterface) {
- return '$prefix${baseType.name}._($value, $library, $ownerFlags)';
- }
- if (baseType is ObjCBlock) {
- return '$prefix${baseType.name}._($value, $library)';
- }
- if (baseType is ObjCObjectPointer) {
- return '${prefix}NSObject._($value, $library, $ownerFlags)';
- }
- return prefix + value;
- }
}
enum ObjCMethodKind {
diff --git a/lib/src/code_generator/objc_nullable.dart b/lib/src/code_generator/objc_nullable.dart
index db57e5f..a8d8c52 100644
--- a/lib/src/code_generator/objc_nullable.dart
+++ b/lib/src/code_generator/objc_nullable.dart
@@ -45,6 +45,39 @@
bool get sameDartAndCType => false;
@override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) {
+ // This is a bit of a hack, but works for all the types that are allowed to
+ // be a child type. If we add more allowed child types, we may have to start
+ // special casing each type. Turns value._id into value?._id ?? nullptr.
+ final convertedValue = child.convertDartTypeToFfiDartType(w, '$value?',
+ objCRetain: objCRetain);
+ return '$convertedValue ?? ${w.ffiLibraryPrefix}.nullptr';
+ }
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) {
+ // All currently supported child types have a Pointer as their FfiDartType.
+ final convertedValue = child.convertFfiDartTypeToDartType(
+ w,
+ value,
+ library,
+ objCRetain: objCRetain,
+ objCEnclosingClass: objCEnclosingClass,
+ );
+ return '$value.address == 0 ? null : $convertedValue';
+ }
+
+ @override
String toString() => '$child?';
@override
diff --git a/lib/src/code_generator/pointer.dart b/lib/src/code_generator/pointer.dart
index 7536395..dcaf597 100644
--- a/lib/src/code_generator/pointer.dart
+++ b/lib/src/code_generator/pointer.dart
@@ -86,4 +86,22 @@
@override
bool get sameDartAndCType => false;
+
+ @override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ ObjCInterface.generateGetId(value, objCRetain);
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ ObjCInterface.generateConstructor('NSObject', value, library, objCRetain);
}
diff --git a/lib/src/code_generator/type.dart b/lib/src/code_generator/type.dart
index 454c783..fff7f0f 100644
--- a/lib/src/code_generator/type.dart
+++ b/lib/src/code_generator/type.dart
@@ -54,8 +54,39 @@
/// Returns whether the dart type and C type string are same.
bool get sameDartAndCType => sameFfiDartAndCType;
- /// Returns the string representation of the Type, for debugging purposes
- /// only. This string should not be printed as generated code.
+ /// Returns generated Dart code that converts the given value from its
+ /// DartType to its FfiDartType.
+ ///
+ /// [value] is the value to be converted. If [objCRetain] is true, the ObjC
+ /// object will be reained (ref count incremented) during conversion.
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ value;
+
+ /// Returns generated Dart code that converts the given value from its
+ /// FfiDartType to its DartType.
+ ///
+ /// [value] is the value to be converted, and [library] is an instance of the
+ /// native library object. If [objCRetain] is true, the ObjC wrapper object
+ /// will retain (ref count increment) the wrapped object pointer. If this
+ /// conversion is occuring in the context of an ObjC class, then
+ /// [objCEnclosingClass] should be the name of the Dart wrapper class (this is
+ /// used by instancetype).
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ value;
+
+ /// Returns a human readable string representation of the Type. This is mostly
+ /// just for debugging, but it may also be used for non-functional code (eg to
+ /// name a variable or type in generated code).
@override
String toString();
@@ -108,6 +139,24 @@
bool get sameDartAndCType => sameFfiDartAndCType;
@override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ value;
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ value;
+
+ @override
String toString() => originalName;
@override
diff --git a/lib/src/code_generator/typealias.dart b/lib/src/code_generator/typealias.dart
index 834987b..fe971a5 100644
--- a/lib/src/code_generator/typealias.dart
+++ b/lib/src/code_generator/typealias.dart
@@ -142,7 +142,15 @@
}
@override
- String getDartType(Writer w) => _dartAliasName ?? type.getDartType(w);
+ String getDartType(Writer w) {
+ if (_dartAliasName != null) {
+ return _dartAliasName!;
+ } else if (type.sameDartAndCType) {
+ return getFfiDartType(w);
+ } else {
+ return type.getDartType(w);
+ }
+ }
@override
bool get sameFfiDartAndCType => type.sameFfiDartAndCType;
@@ -151,6 +159,30 @@
bool get sameDartAndCType => type.sameDartAndCType;
@override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ type.convertDartTypeToFfiDartType(w, value, objCRetain: objCRetain);
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ type.convertFfiDartTypeToDartType(
+ w,
+ value,
+ library,
+ objCRetain: objCRetain,
+ objCEnclosingClass: objCEnclosingClass,
+ );
+
+ @override
String cacheKey() => type.cacheKey();
@override
@@ -173,4 +205,25 @@
super.genFfiDartType,
super.isInternal,
}) : super._();
+
+ @override
+ String convertDartTypeToFfiDartType(
+ Writer w,
+ String value, {
+ required bool objCRetain,
+ }) =>
+ ObjCInterface.generateGetId(value, objCRetain);
+
+ @override
+ String convertFfiDartTypeToDartType(
+ Writer w,
+ String value,
+ String library, {
+ required bool objCRetain,
+ String? objCEnclosingClass,
+ }) =>
+ // objCEnclosingClass must be present, because instancetype can only
+ // occur inside a class.
+ ObjCInterface.generateConstructor(
+ objCEnclosingClass!, value, library, objCRetain);
}
diff --git a/test/native_objc_test/block_config.yaml b/test/native_objc_test/block_config.yaml
index e83c6b7..841439e 100644
--- a/test/native_objc_test/block_config.yaml
+++ b/test/native_objc_test/block_config.yaml
@@ -1,5 +1,5 @@
name: BlockTestObjCLibrary
-description: 'Tests calling Objective-C blocks'
+description: 'Tests calling Objective-C blocks.'
language: objc
output: 'block_bindings.dart'
exclude-all-by-default: true
diff --git a/test/native_objc_test/block_test.dart b/test/native_objc_test/block_test.dart
index 0253eaf..7ca61d6 100644
--- a/test/native_objc_test/block_test.dart
+++ b/test/native_objc_test/block_test.dart
@@ -21,6 +21,9 @@
typedef DoubleBlock = ObjCBlock_ffiDouble_ffiDouble;
typedef Vec4Block = ObjCBlock_Vec4_Vec4;
typedef VoidBlock = ObjCBlock_ffiVoid;
+typedef ObjectBlock = ObjCBlock_DummyObject_DummyObject;
+typedef NullableObjectBlock = ObjCBlock_DummyObject_DummyObject1;
+typedef BlockBlock = ObjCBlock_Int32Int32_Int32Int32;
void main() {
late BlockTestObjCLibrary lib;
@@ -125,41 +128,111 @@
});
test('Struct block', () {
- final inputPtr = calloc<Vec4>();
- final input = inputPtr.ref;
- input.x = 1.2;
- input.y = 3.4;
- input.z = 5.6;
- input.w = 7.8;
+ using((Arena arena) {
+ final inputPtr = arena<Vec4>();
+ final input = inputPtr.ref;
+ input.x = 1.2;
+ input.y = 3.4;
+ input.z = 5.6;
+ input.w = 7.8;
- final tempPtr = calloc<Vec4>();
- final temp = tempPtr.ref;
- final block = Vec4Block.fromFunction(lib, (Vec4 v) {
- // Twiddle the Vec4 components.
- temp.x = v.y;
- temp.y = v.z;
- temp.z = v.w;
- temp.w = v.x;
- return temp;
+ final tempPtr = arena<Vec4>();
+ final temp = tempPtr.ref;
+ final block = Vec4Block.fromFunction(lib, (Vec4 v) {
+ // Twiddle the Vec4 components.
+ temp.x = v.y;
+ temp.y = v.z;
+ temp.z = v.w;
+ temp.w = v.x;
+ return temp;
+ });
+
+ final result1 = block(input);
+ expect(result1.x, 3.4);
+ expect(result1.y, 5.6);
+ expect(result1.z, 7.8);
+ expect(result1.w, 1.2);
+
+ final result2Ptr = arena<Vec4>();
+ final result2 = result2Ptr.ref;
+ BlockTester.callVec4Block_(lib, result2Ptr, block);
+ expect(result2.x, 3.4);
+ expect(result2.y, 5.6);
+ expect(result2.z, 7.8);
+ expect(result2.w, 1.2);
+ });
+ });
+
+ test('Object block', () {
+ bool isCalled = false;
+ final block = ObjectBlock.fromFunction(lib, (DummyObject x) {
+ isCalled = true;
+ return x;
});
- final result1 = block(input);
- expect(result1.x, 3.4);
- expect(result1.y, 5.6);
- expect(result1.z, 7.8);
- expect(result1.w, 1.2);
+ final obj = DummyObject.new1(lib);
+ final result1 = block(obj);
+ expect(result1, obj);
+ expect(isCalled, isTrue);
- final result2Ptr = calloc<Vec4>();
- final result2 = result2Ptr.ref;
- BlockTester.callVec4Block_(lib, result2Ptr, block);
- expect(result2.x, 3.4);
- expect(result2.y, 5.6);
- expect(result2.z, 7.8);
- expect(result2.w, 1.2);
+ isCalled = false;
+ final result2 = BlockTester.callObjectBlock_(lib, block);
+ expect(result2, isNot(obj));
+ expect(result2.pointer, isNot(nullptr));
+ expect(isCalled, isTrue);
+ });
- calloc.free(inputPtr);
- calloc.free(tempPtr);
- calloc.free(result2Ptr);
+ test('Nullable object block', () {
+ bool isCalled = false;
+ final block = NullableObjectBlock.fromFunction(lib, (DummyObject? x) {
+ isCalled = true;
+ return x;
+ });
+
+ final obj = DummyObject.new1(lib);
+ final result1 = block(obj);
+ expect(result1, obj);
+ expect(isCalled, isTrue);
+
+ isCalled = false;
+ final result2 = block(null);
+ expect(result2, isNull);
+ expect(isCalled, isTrue);
+
+ isCalled = false;
+ final result3 = BlockTester.callNullableObjectBlock_(lib, block);
+ expect(result3, isNull);
+ expect(isCalled, isTrue);
+ });
+
+ test('Block block', () {
+ final blockBlock = BlockBlock.fromFunction(lib, (IntBlock intBlock) {
+ return IntBlock.fromFunction(lib, (int x) {
+ return 3 * intBlock(x);
+ });
+ });
+
+ final intBlock = IntBlock.fromFunction(lib, (int x) {
+ return 5 * x;
+ });
+ final result1 = blockBlock(intBlock);
+ expect(result1(1), 15);
+
+ final result2 = BlockTester.newBlock_withMult_(lib, blockBlock, 2);
+ expect(result2(1), 6);
+ });
+
+ test('Native block block', () {
+ final blockBlock = BlockTester.newBlockBlock_(lib, 7);
+
+ final intBlock = IntBlock.fromFunction(lib, (int x) {
+ return 5 * x;
+ });
+ final result1 = blockBlock(intBlock);
+ expect(result1(1), 35);
+
+ final result2 = BlockTester.newBlock_withMult_(lib, blockBlock, 2);
+ expect(result2(1), 14);
});
Pointer<Void> funcPointerBlockRefCountTest() {
@@ -172,7 +245,7 @@
test('Function pointer block ref counting', () {
final rawBlock = funcPointerBlockRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, rawBlock.cast()), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, rawBlock), 0);
});
Pointer<Void> funcBlockRefCountTest() {
@@ -181,10 +254,208 @@
return block.pointer.cast();
}
- test('Function pointer block ref counting', () {
+ test('Function block ref counting', () {
final rawBlock = funcBlockRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, rawBlock.cast()), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, rawBlock), 0);
+ });
+
+ (Pointer<Void>, Pointer<Void>, Pointer<Void>)
+ blockBlockDartCallRefCountTest() {
+ final inputBlock = IntBlock.fromFunction(lib, (int x) {
+ return 5 * x;
+ });
+ final blockBlock = BlockBlock.fromFunction(lib, (IntBlock intBlock) {
+ return IntBlock.fromFunction(lib, (int x) {
+ return 3 * intBlock(x);
+ });
+ });
+ final outputBlock = blockBlock(inputBlock);
+ expect(outputBlock(1), 15);
+ doGC();
+
+ // One reference held by inputBlock object, another bound to the
+ // outputBlock lambda.
+ expect(
+ BlockTester.getBlockRetainCount_(lib, inputBlock.pointer.cast()), 2);
+
+ expect(
+ BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
+ expect(
+ BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ return (
+ inputBlock.pointer.cast(),
+ blockBlock.pointer.cast(),
+ outputBlock.pointer.cast()
+ );
+ }
+
+ test('Calling a block block from Dart has correct ref counting', () {
+ final (inputBlock, blockBlock, outputBlock) =
+ blockBlockDartCallRefCountTest();
+ doGC();
+
+ // This leaks because block functions aren't cleaned up at the moment.
+ // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak.
+ expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 1);
+
+ expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ });
+
+ (Pointer<Void>, Pointer<Void>, Pointer<Void>)
+ blockBlockObjCCallRefCountTest() {
+ late Pointer<Void> inputBlock;
+ final blockBlock = BlockBlock.fromFunction(lib, (IntBlock intBlock) {
+ inputBlock = intBlock.pointer.cast();
+ return IntBlock.fromFunction(lib, (int x) {
+ return 3 * intBlock(x);
+ });
+ });
+ final outputBlock = BlockTester.newBlock_withMult_(lib, blockBlock, 2);
+ expect(outputBlock(1), 6);
+ doGC();
+
+ expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 2);
+ expect(
+ BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
+ expect(
+ BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ return (
+ inputBlock,
+ blockBlock.pointer.cast(),
+ outputBlock.pointer.cast()
+ );
+ }
+
+ test('Calling a block block from ObjC has correct ref counting', () {
+ final (inputBlock, blockBlock, outputBlock) =
+ blockBlockObjCCallRefCountTest();
+ doGC();
+
+ // This leaks because block functions aren't cleaned up at the moment.
+ // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak.
+ expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 2);
+
+ expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ });
+
+ (Pointer<Void>, Pointer<Void>, Pointer<Void>)
+ nativeBlockBlockDartCallRefCountTest() {
+ final inputBlock = IntBlock.fromFunction(lib, (int x) {
+ return 5 * x;
+ });
+ final blockBlock = BlockTester.newBlockBlock_(lib, 7);
+ final outputBlock = blockBlock(inputBlock);
+ expect(outputBlock(1), 35);
+ doGC();
+
+ // One reference held by inputBlock object, another held internally by the
+ // ObjC implementation of the blockBlock.
+ expect(
+ BlockTester.getBlockRetainCount_(lib, inputBlock.pointer.cast()), 2);
+
+ expect(
+ BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
+ expect(
+ BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ return (
+ inputBlock.pointer.cast(),
+ blockBlock.pointer.cast(),
+ outputBlock.pointer.cast()
+ );
+ }
+
+ test('Calling a native block block from Dart has correct ref counting', () {
+ final (inputBlock, blockBlock, outputBlock) =
+ nativeBlockBlockDartCallRefCountTest();
+ doGC();
+ expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ });
+
+ (Pointer<Void>, Pointer<Void>) nativeBlockBlockObjCCallRefCountTest() {
+ final blockBlock = BlockTester.newBlockBlock_(lib, 7);
+ final outputBlock = BlockTester.newBlock_withMult_(lib, blockBlock, 2);
+ expect(outputBlock(1), 14);
+ doGC();
+
+ expect(
+ BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
+ expect(
+ BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ return (blockBlock.pointer.cast(), outputBlock.pointer.cast());
+ }
+
+ test('Calling a native block block from ObjC has correct ref counting', () {
+ final (blockBlock, outputBlock) = nativeBlockBlockObjCCallRefCountTest();
+ doGC();
+ expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
+ expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ });
+
+ (Pointer<Int32>, Pointer<Int32>) objectBlockRefCountTest(Allocator alloc) {
+ final inputCounter = alloc<Int32>();
+ final outputCounter = alloc<Int32>();
+ inputCounter.value = 0;
+ outputCounter.value = 0;
+
+ final block = ObjectBlock.fromFunction(lib, (DummyObject x) {
+ return DummyObject.newWithCounter_(lib, outputCounter);
+ });
+
+ final inputObj = DummyObject.newWithCounter_(lib, inputCounter);
+ final outputObj = block(inputObj);
+ expect(inputCounter.value, 1);
+ expect(outputCounter.value, 1);
+
+ return (inputCounter, outputCounter);
+ }
+
+ test('Objects received and returned by blocks have correct ref counts', () {
+ using((Arena arena) {
+ final (inputCounter, outputCounter) = objectBlockRefCountTest(arena);
+ doGC();
+ expect(inputCounter.value, 0);
+ expect(outputCounter.value, 0);
+ });
+ });
+
+ (Pointer<Int32>, Pointer<Int32>) objectNativeBlockRefCountTest(
+ Allocator alloc) {
+ final inputCounter = alloc<Int32>();
+ final outputCounter = alloc<Int32>();
+ inputCounter.value = 0;
+ outputCounter.value = 0;
+
+ final block = ObjectBlock.fromFunction(lib, (DummyObject x) {
+ x.setCounter_(inputCounter);
+ return DummyObject.newWithCounter_(lib, outputCounter);
+ });
+
+ final outputObj = BlockTester.callObjectBlock_(lib, block);
+ expect(inputCounter.value, 1);
+ expect(outputCounter.value, 1);
+
+ return (inputCounter, outputCounter);
+ }
+
+ test(
+ 'Objects received and returned by native blocks have correct ref counts',
+ () {
+ using((Arena arena) {
+ final (inputCounter, outputCounter) =
+ objectNativeBlockRefCountTest(arena);
+ doGC();
+
+ // This leaks because block functions aren't cleaned up at the moment.
+ // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak.
+ expect(inputCounter.value, 1);
+
+ expect(outputCounter.value, 0);
+ });
});
test('Block fields have sensible values', () {
diff --git a/test/native_objc_test/block_test.m b/test/native_objc_test/block_test.m
index a864f2e..3dd4289 100644
--- a/test/native_objc_test/block_test.m
+++ b/test/native_objc_test/block_test.m
@@ -12,11 +12,47 @@
double w;
} Vec4;
+@interface DummyObject : NSObject {
+ int32_t* counter;
+}
++ (instancetype)newWithCounter:(int32_t*) _counter;
+- (instancetype)initWithCounter:(int32_t*) _counter;
+- (void)setCounter:(int32_t*) _counter;
+- (void)dealloc;
+@end
+@implementation DummyObject
+
++ (instancetype)newWithCounter:(int32_t*) _counter {
+ return [[DummyObject alloc] initWithCounter: _counter];
+}
+
+- (instancetype)initWithCounter:(int32_t*) _counter {
+ counter = _counter;
+ ++*counter;
+ return [super init];
+}
+
+- (void)setCounter:(int32_t*) _counter {
+ counter = _counter;
+ ++*counter;
+}
+
+- (void)dealloc {
+ if (counter != nil) --*counter;
+ [super dealloc];
+}
+
+@end
+
+
typedef int32_t (^IntBlock)(int32_t);
typedef float (^FloatBlock)(float);
typedef double (^DoubleBlock)(double);
typedef Vec4 (^Vec4Block)(Vec4);
typedef void (^VoidBlock)();
+typedef DummyObject* (^ObjectBlock)(DummyObject*);
+typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable);
+typedef IntBlock (^BlockBlock)(IntBlock);
// Wrapper around a block, so that our Dart code can test creating and invoking
// blocks in Objective C code.
@@ -34,6 +70,10 @@
+ (float)callFloatBlock:(FloatBlock)block;
+ (double)callDoubleBlock:(DoubleBlock)block;
+ (Vec4)callVec4Block:(Vec4Block)block;
++ (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED;
++ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block;
++ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult;
++ (BlockBlock)newBlockBlock:(int)mult;
@end
@implementation BlockTester
@@ -116,4 +156,27 @@
return block(vec4);
}
++ (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED {
+ return block([DummyObject new]);
+}
+
++ (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block {
+ return block(nil);
+}
+
++ (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult {
+ return block([^int(int x) {
+ return mult * x;
+ } copy]);
+ // ^ copy this stack allocated block to the heap.
+}
+
++ (BlockBlock)newBlockBlock:(int)mult {
+ return [^IntBlock(IntBlock block) {
+ return [^int(int x) {
+ return mult * block(x);
+ } copy];
+ } copy];
+}
+
@end
diff --git a/test/native_objc_test/nullable_test.dart b/test/native_objc_test/nullable_test.dart
index 9aed94e..b264531 100644
--- a/test/native_objc_test/nullable_test.dart
+++ b/test/native_objc_test/nullable_test.dart
@@ -64,6 +64,13 @@
expect(NullableInterface.isNullWithNotNullableNSObjectPtrArg_(lib, obj),
false);
});
+
+ test('Explicit non null', () {
+ expect(
+ NullableInterface.isNullWithExplicitNonNullableNSObjectPtrArg_(
+ lib, obj),
+ false);
+ });
});
});
}
diff --git a/test/native_objc_test/nullable_test.m b/test/native_objc_test/nullable_test.m
index abf1b02..06d257d 100644
--- a/test/native_objc_test/nullable_test.m
+++ b/test/native_objc_test/nullable_test.m
@@ -5,6 +5,7 @@
+(BOOL) isNullWithNullableNSObjectArg:(nullable NSObject *)x;
+(BOOL) isNullWithNotNullableNSObjectPtrArg:(NSObject *)x;
++(BOOL) isNullWithExplicitNonNullableNSObjectPtrArg:(nonnull NSObject *)x;
+(nullable NSObject *) returnNil:(BOOL)r;
@property (nullable, retain) NSObject *nullableObjectProperty;
@@ -21,6 +22,10 @@
return x == NULL;
}
++(BOOL) isNullWithExplicitNonNullableNSObjectPtrArg:(nonnull NSObject *)x {
+ return x == NULL;
+}
+
+(nullable NSObject *) returnNil:(BOOL)r {
if (r) {
return nil;