blob: 9fec93b2d59c0a14d3166592af121cc98715e846 [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 '../code_generator.dart';
import '../context.dart';
import '../header_parser/sub_parsers/api_availability.dart';
import '../visitor/ast.dart';
import 'binding_string.dart';
import 'scope.dart';
import 'utils.dart';
import 'writer.dart';
class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope {
@override
final Context context;
ObjCInterface? superType;
bool filled = false;
final String lookupName;
late final ObjCInternalGlobal classObject;
late final ObjCInternalGlobal _isKindOfClass;
late final ObjCMsgSendFunc _isKindOfClassMsgSend;
final protocols = <ObjCProtocol>[];
final categories = <ObjCCategory>[];
final subtypes = <ObjCInterface>[];
final ApiAvailability apiAvailability;
// Filled by ListBindingsVisitation.
bool generateAsStub = false;
ObjCInterface({
super.usr,
required String super.originalName,
String? name,
String? lookupName,
super.dartDoc,
required this.apiAvailability,
required this.context,
}) : lookupName = lookupName ?? originalName,
super(
name:
context.objCBuiltInFunctions.getBuiltInInterfaceName(
originalName,
) ??
name ??
originalName,
) {
classObject = ObjCInternalGlobal(
'_class_$originalName',
() => '${ObjCBuiltInFunctions.getClass.gen(context)}("$lookupName")',
);
_isKindOfClass = context.objCBuiltInFunctions.getSelObject(
'isKindOfClass:',
);
_isKindOfClassMsgSend = context.objCBuiltInFunctions.getMsgSendFunc(
BooleanType(),
[
Parameter(
name: 'clazz',
type: PointerType(objCObjectType),
objCConsumed: false,
),
],
);
}
void addProtocol(ObjCProtocol? proto) {
if (proto != null) protocols.add(proto);
}
@override
bool get isObjCImport =>
context.objCBuiltInFunctions.getBuiltInInterfaceName(originalName) !=
null;
bool get unavailable => apiAvailability.availability == Availability.none;
@override
BindingString toBindingString(Writer w) {
final context = w.context;
final s = StringBuffer();
s.write('\n');
if (generateAsStub) {
s.write('''
/// WARNING: $name is a stub. To generate bindings for this class, include
/// $originalName in your config's objc-interfaces list.
///
''');
}
s.write(makeDartDoc(dartDoc));
final ctorBody = [
apiAvailability.runtimeCheck(
ObjCBuiltInFunctions.checkOsVersion.gen(context),
originalName,
),
if (!generateAsStub) 'assert(isA(object\$));',
].nonNulls.join('\n ');
final rawObjType = PointerType(objCObjectType).getCType(context);
final wrapObjType = ObjCBuiltInFunctions.objectBase.gen(context);
final protos = [
wrapObjType,
...[superType, ...protocols].nonNulls.map((p) => p.getDartType(context)),
];
s.write('''
extension type $name._($wrapObjType object\$) implements ${protos.join(',')} {
/// Constructs a [$name] that points to the same underlying object as [other].
$name.as($wrapObjType other) : object\$ = other {
$ctorBody
}
/// Constructs a [$name] that wraps the given raw object pointer.
$name.fromPointer($rawObjType other,
{bool retain = false, bool release = false}) :
object\$ = $wrapObjType(other, retain: retain, release: release) {
$ctorBody
}
${generateAsStub ? '' : _generateStaticMethods(w)}
}
''');
if (!generateAsStub) {
s.write('''
extension $name\$Methods on $name {
${generateInstanceMethodBindings(w, this)}
}
''');
}
return BindingString(
type: BindingStringType.objcInterface,
string: s.toString(),
);
}
String _generateStaticMethods(Writer w) {
final context = w.context;
final wrapObjType = ObjCBuiltInFunctions.objectBase.gen(context);
final s = StringBuffer();
final isKindOfClass = _isKindOfClassMsgSend.invoke(
context,
'obj.ref.pointer',
_isKindOfClass.name,
[classObject.name],
);
s.write('''
/// Returns whether [obj] is an instance of [$name].
static bool isA($wrapObjType obj) => $isKindOfClass;
''');
s.write(generateStaticMethodBindings(w, this));
final newMethod = methods
.where(
(ObjCMethod m) =>
m.isClassMethod &&
m.family == ObjCMethodFamily.new_ &&
m.params.isEmpty &&
m.originalName == 'new',
)
.firstOrNull;
if (newMethod != null && originalName != 'NSString') {
s.write('''
/// Returns a new instance of $name constructed with the default `new` method.
$name() : this.as(${newMethod.name}().object\$);
''');
}
return s.toString();
}
@override
String getCType(Context context) =>
PointerType(objCObjectType).getCType(context);
@override
String getDartType(Context context) =>
isObjCImport ? '${context.libs.prefix(objcPkgImport)}.$name' : name;
@override
String getNativeType({String varName = ''}) => 'id $varName';
@override
String getObjCBlockSignatureType(Context context) => getDartType(context);
@override
bool get sameFfiDartAndCType => true;
@override
bool get sameDartAndCType => false;
@override
bool get sameDartAndFfiDartType => false;
@override
String convertDartTypeToFfiDartType(
Context context,
String value, {
required bool objCRetain,
required bool objCAutorelease,
}) => ObjCInterface.generateGetId(value, objCRetain, objCAutorelease);
static String generateGetId(
String value,
bool objCRetain,
bool objCAutorelease,
) => objCRetain
? (objCAutorelease
? '$value.ref.retainAndAutorelease()'
: '$value.ref.retainAndReturnPointer()')
: (objCAutorelease ? '$value.ref.autorelease()' : '$value.ref.pointer');
@override
String convertFfiDartTypeToDartType(
Context context,
String value, {
required bool objCRetain,
String? objCEnclosingClass,
}) => ObjCInterface.generateConstructor(
getDartType(context),
value,
objCRetain,
);
static String generateConstructor(
String className,
String value,
bool objCRetain,
) {
final ownershipFlags = 'retain: $objCRetain, release: true';
return '$className.fromPointer($value, $ownershipFlags)';
}
@override
String? generateRetain(String value) =>
'(__bridge id)(__bridge_retained void*)($value)';
@override
void visit(Visitation visitation) => visitation.visitObjCInterface(this);
// Set typeGraphOnly to true to skip iterating methods and other children, and
// just iterate the DAG of interfaces, categories, and protocols. This is
// useful for visitors that need to ensure super types are visited first.
@override
void visitChildren(Visitor visitor, {bool typeGraphOnly = false}) {
if (!typeGraphOnly) {
super.visitChildren(visitor);
visitor.visit(classObject);
visitor.visit(_isKindOfClass);
visitor.visit(_isKindOfClassMsgSend);
visitMethods(visitor);
visitor.visit(objcPkgImport);
// In the type DAG, categories link to their parent interface, not the
// other way around. So don't iterate these categories as part of the DAG.
visitor.visitAll(categories);
}
visitor.visit(superType);
visitor.visitAll(protocols);
// Note: Don't visit subtypes here, because they shouldn't affect transitive
// inclusion. Including an interface shouldn't auto-include all its
// subtypes, even as stubs.
}
@override
bool isSupertypeOf(Type other) {
other = other.typealiasType;
if (other is ObjCInterface) {
for (ObjCInterface? t = other; t != null; t = t.superType) {
if (t == this) return true;
}
}
return false;
}
}