blob: de76111a8d555fc753acc23e0c9a846e76fe6e38 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:ffigen/src/code_generator.dart';
import 'binding_string.dart';
import 'utils.dart';
import 'writer.dart';
// Class methods defined on NSObject that we don't want to copy to child objects
// by default.
const _excludedNSObjectClassMethods = {
'allocWithZone:',
'copyWithZone:',
'mutableCopyWithZone:',
'instancesRespondToSelector:',
'conformsToProtocol:',
'instanceMethodForSelector:',
'instanceMethodSignatureForSelector:',
'isSubclassOfClass:',
'resolveClassMethod:',
'resolveInstanceMethod:',
'hash',
'superclass',
'class',
'description',
'debugDescription',
};
class _ObjCBuiltInFunctions {
late final _registerNameFunc = Func(
name: '_sel_registerName',
originalName: 'sel_registerName',
returnType: Type.pointer(Type.struct(objCSelType)),
parameters: [
Parameter(name: 'str', type: Type.pointer(Type.importedType(charType)))
],
);
late final String registerName;
late final _getClassFunc = Func(
name: '_objc_getClass',
originalName: 'objc_getClass',
returnType: Type.pointer(Type.struct(objCObjectType)),
parameters: [
Parameter(name: 'str', type: Type.pointer(Type.importedType(charType)))
],
);
late final String getClass;
// We need to load a separate instance of objc_msgSend for each signature.
final _msgSendFuncs = <String, Func>{};
Func getMsgSendFunc(Type returnType, List<ObjCMethodParam> params) {
// TODO(#279): These keys don't dedupe sufficiently.
var key = returnType.hashCode.toRadixString(36);
for (final p in params) {
key += ' ' + p.type.hashCode.toRadixString(36);
}
_msgSendFuncs[key] ??= Func(
name: '_objc_msgSend_${_msgSendFuncs.length}',
originalName: 'objc_msgSend',
returnType: returnType,
parameters: [
Parameter(name: 'obj', type: Type.pointer(Type.struct(objCObjectType))),
Parameter(name: 'sel', type: Type.pointer(Type.struct(objCSelType))),
for (final p in params) Parameter(name: p.name, type: p.type.type),
],
);
return _msgSendFuncs[key]!;
}
bool utilsExist = false;
void ensureUtilsExist(Writer w, StringBuffer s) {
if (utilsExist) return;
utilsExist = true;
registerName = w.topLevelUniqueNamer.makeUnique('_registerName');
final selType = _registerNameFunc.functionType.returnType.getCType(w);
s.write('\n$selType $registerName(${w.className} _lib, String name) {\n');
s.write(' final cstr = name.toNativeUtf8();\n');
s.write(' final sel = _lib.${_registerNameFunc.name}(cstr.cast());\n');
s.write(' ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
s.write(' return sel;\n');
s.write('}\n');
getClass = w.topLevelUniqueNamer.makeUnique('_getClass');
final objType = _getClassFunc.functionType.returnType.getCType(w);
s.write('\n$objType $getClass(${w.className} _lib, String name) {\n');
s.write(' final cstr = name.toNativeUtf8();\n');
s.write(' final clazz = _lib.${_getClassFunc.name}(cstr.cast());\n');
s.write(' ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);\n');
s.write(' return clazz;\n');
s.write('}\n');
s.write('\nclass _ObjCWrapper {\n');
s.write(' final $objType _id;\n');
s.write(' final ${w.className} _lib;\n');
s.write(' _ObjCWrapper._(this._id, this._lib);\n');
s.write('}\n');
}
void addDependencies(Set<Binding> dependencies) {
_registerNameFunc.addDependencies(dependencies);
_getClassFunc.addDependencies(dependencies);
for (final func in _msgSendFuncs.values) {
func.addDependencies(dependencies);
}
}
}
final _builtInFunctions = _ObjCBuiltInFunctions();
class ObjCInterface extends NoLookUpBinding {
ObjCInterface? superType;
final methods = <ObjCMethod>[];
bool filled = false;
// Objective C supports overriding class methods, but Dart doesn't support
// overriding static methods. So in our generated Dart code, child classes
// must explicitly implement all the class methods of their super type. To
// help with this, we store the class methods in this map, as well as in the
// methods list.
final classMethods = <String, ObjCMethod>{};
ObjCInterface({
String? usr,
required String originalName,
required String name,
String? dartDoc,
}) : super(
usr: usr,
originalName: originalName,
name: name,
dartDoc: dartDoc,
);
@override
BindingString toBindingString(Writer w) {
final s = StringBuffer();
if (dartDoc != null) {
s.write(makeDartDoc(dartDoc!));
}
final uniqueNamer = UniqueNamer({name});
final natLib = w.className;
_builtInFunctions.ensureUtilsExist(w, s);
final objType = Type.pointer(Type.struct(objCObjectType)).getCType(w);
final selType = Type.pointer(Type.struct(objCSelType)).getCType(w);
// Class declaration.
s.write('class $name ');
uniqueNamer.markUsed('_id');
s.write('extends ${superType?.name ?? '_ObjCWrapper'} {\n');
s.write(' $name._($objType id, $natLib lib) : super._(id, lib);\n\n');
// Class object, used to call static methods.
final classObject = uniqueNamer.makeUnique('_class');
s.write(' static $objType? $classObject;\n\n');
// Cast method.
s.write(' static $name castFrom<T extends _ObjCWrapper>(T other) {\n');
s.write(' return $name._(other._id, other._lib);\n');
s.write(' }\n\n');
// Methods.
for (final m in methods) {
final methodName = m._getDartMethodName(uniqueNamer);
final selName = uniqueNamer.makeUnique('_sel_$methodName');
final isStatic = m.kind == ObjCMethodKind.classMethod;
// SEL object for the method.
s.write(' static $selType? $selName;');
// The method declaration.
if (m.dartDoc != null) {
s.write(makeDartDoc(m.dartDoc!));
}
s.write(' ');
if (isStatic) s.write('static ');
s.write('${m.returnType!.getConvertedType(w, name)} ');
if (m.kind == ObjCMethodKind.propertyGetter) s.write('get ');
if (m.kind == ObjCMethodKind.propertySetter) s.write('set ');
s.write(methodName);
if (m.kind != ObjCMethodKind.propertyGetter) {
s.write('(');
var first = true;
if (isStatic) {
first = false;
s.write('$natLib _lib');
}
for (final p in m.params) {
if (first) {
first = false;
} else {
s.write(', ');
}
s.write('${p.type.getConvertedType(w, name)} ${p.name}');
}
s.write(')');
}
s.write(' {\n');
// Implementation.
if (isStatic) {
s.write(' $classObject ??= '
'${_builtInFunctions.getClass}(_lib, "$originalName");\n');
}
s.write(' $selName ??= '
'${_builtInFunctions.registerName}(_lib, "${m.originalName}");\n');
final convertReturn = m.returnType!.needsConverting;
s.write(' ${convertReturn ? 'final _ret = ' : 'return '}');
s.write('_lib.${m.msgSend!.name}(');
s.write(isStatic ? '_class!' : '_id');
s.write(', $selName!');
for (final p in m.params) {
s.write(', ${p.type.doArgConversion(p.name)}');
}
s.write(');\n');
if (convertReturn) {
final result = m.returnType!.doReturnConversion('_ret', name, '_lib');
s.write(' return $result;');
}
s.write(' }\n\n');
}
s.write('}\n\n');
return BindingString(
type: BindingStringType.objcInterface, string: s.toString());
}
@override
void addDependencies(Set<Binding> dependencies) {
if (dependencies.contains(this)) return;
dependencies.add(this);
if (superType != null) {
superType!.addDependencies(dependencies);
// Copy class methods from the super type, because Dart classes don't
// inherit static methods.
for (final m in superType!.classMethods.values) {
if (!_excludedNSObjectClassMethods.contains(m.originalName)) {
addMethod(m);
}
}
}
for (final m in methods) {
m.addDependencies(dependencies);
}
_builtInFunctions.addDependencies(dependencies);
}
void addMethod(ObjCMethod method) {
methods.add(method);
if (method.kind == ObjCMethodKind.classMethod) {
classMethods[method.originalName] ??= method;
}
}
}
enum ObjCMethodKind {
instanceMethod,
classMethod,
propertyGetter,
propertySetter,
}
class ObjCProperty {
final String originalName;
String? dartName;
ObjCProperty(this.originalName);
}
class ObjCMethod {
final String? dartDoc;
final String originalName;
final ObjCProperty? property;
ObjCMethodType? returnType;
final params = <ObjCMethodParam>[];
final ObjCMethodKind kind;
Func? msgSend;
ObjCMethod({
required this.originalName,
this.property,
this.dartDoc,
required this.kind,
});
void addDependencies(Set<Binding> dependencies) {
returnType!.type.addDependencies(dependencies);
for (final p in params) {
p.type.type.addDependencies(dependencies);
}
msgSend = _builtInFunctions.getMsgSendFunc(returnType!.type, params);
}
String _getDartMethodName(UniqueNamer uniqueNamer) {
if (property != null) {
// A getter and a setter are allowed to have the same name, so we can't
// just run the name through uniqueNamer. Instead they need to share
// the dartName, which is run through uniqueNamer.
if (property!.dartName == null) {
property!.dartName = uniqueNamer.makeUnique(property!.originalName);
}
return property!.dartName!;
}
// Objective C methods can look like:
// foo
// foo:
// foo:someArgName:
// If there is a trailing ':', omit it. Replace all other ':' with '_'.
var name = originalName;
final index = name.indexOf(':');
if (index != -1) name = name.substring(0, index);
return uniqueNamer.makeUnique(name.replaceAll(':', '_'));
}
}
class ObjCMethodParam {
final ObjCMethodType type;
final String name;
ObjCMethodParam(Type t, this.name) : type = ObjCMethodType(t);
}
// Wrapper around Type with helper methods 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 class.
class ObjCMethodType {
final Type type;
ObjCMethodType(this.type);
bool get isObject {
if (type.broadType != BroadType.Pointer) return false;
final child = type.child!;
if (child.broadType != BroadType.Compound) return false;
return child.compound == objCObjectType;
}
bool get isInstanceType {
if (type.broadType != BroadType.Typealias) return false;
final alias = type.typealias!;
if (alias.name != 'instancetype') return false;
return ObjCMethodType(alias.type).isObject;
}
bool get isInterface => type.broadType == BroadType.ObjCInterface;
bool get isBool => type.broadType == BroadType.Boolean;
bool get needsConverting =>
isInterface || isBool || isObject || isInstanceType;
String getConvertedType(Writer w, String enclosingClass) {
if (isBool) return 'bool';
if (isInterface) return type.objCInterface!.name;
if (isObject) return 'NSObject';
if (isInstanceType) return enclosingClass;
return type.getDartType(w);
}
String doArgConversion(String value) {
if (isBool) return '$value ? 1 : 0';
if (isInterface || isObject || isInstanceType) return '$value._id';
return value;
}
String doReturnConversion(
String value, String enclosingClass, String library) {
if (isBool) return '$value != 0';
if (isInterface) return '${type.objCInterface!.name}._($value, $library)';
if (isObject) return 'NSObject._($value, $library)';
if (isInstanceType) return '$enclosingClass._($value, $library)';
return value;
}
}