blob: cd3de4bc07e35f6a7186e39b6519fd697ad2ab0f [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 '../../config_provider/config.dart';
import '../../config_provider/config_types.dart';
import '../../context.dart';
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../utils.dart';
import 'api_availability.dart';
import 'objcprotocoldecl_parser.dart';
String applyModulePrefix(String name, String? module) =>
module == null ? name : '$module.$name';
Type? parseObjCInterfaceDeclaration(
Context context,
clang_types.CXCursor cursor,
) {
final usr = cursor.usr();
final cachedItf = context.bindingsIndex.getSeenObjCInterface(usr);
if (cachedItf != null) return cachedItf;
final name = cursor.spelling();
final decl = Declaration(usr: usr, originalName: name);
final apiAvailability = ApiAvailability.fromCursor(cursor, context);
final config = context.config;
final objcInterfaces = config.objectiveC?.interfaces;
if (objcInterfaces == null) {
return null;
}
context.logger.fine(
'++++ Adding ObjC interface: '
'Name: $name, ${cursor.completeStringRepr()}',
);
final itf = ObjCInterface(
context: context,
usr: usr,
originalName: name,
name: objcInterfaces.rename(decl),
lookupName: applyModulePrefix(name, objcInterfaces.module(decl)),
dartDoc: getCursorDocComment(
context,
cursor,
fallbackComment: name,
availability: apiAvailability.dartDoc,
),
apiAvailability: apiAvailability,
);
context.bindingsIndex.addObjCInterfaceToSeen(usr, itf);
return itf;
}
void fillObjCInterfaceMethodsIfNeeded(
Context context,
ObjCInterface itf,
clang_types.CXCursor cursor,
) {
if (_isClassDeclaration(cursor)) {
// @class declarations are ObjC's way of forward declaring classes. In that
// case there's nothing to fill yet.
return;
}
if (itf.filled) return;
itf.filled = true; // Break cycles.
final objcInterfaces = context.config.objectiveC!.interfaces;
context.logger.fine(
'++++ Filling ObjC interface: '
'Name: ${itf.originalName}, ${cursor.completeStringRepr()}',
);
final itfDecl = Declaration(usr: itf.usr, originalName: itf.originalName);
cursor.visitChildren((child) {
switch (child.kind) {
case clang_types.CXCursorKind.CXCursor_ObjCSuperClassRef:
_parseSuperType(context, child, itf);
break;
case clang_types.CXCursorKind.CXCursor_ObjCProtocolRef:
final protoCursor = clang.clang_getCursorDefinition(child);
itf.addProtocol(parseObjCProtocolDeclaration(context, protoCursor));
break;
case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl:
final (getter, setter) = parseObjCProperty(
context,
child,
itfDecl,
objcInterfaces,
);
itf.addMethod(getter);
itf.addMethod(setter);
break;
case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
itf.addMethod(parseObjCMethod(context, child, itfDecl, objcInterfaces));
break;
}
});
context.logger.fine(
'++++ Finished ObjC interface: '
'Name: ${itf.originalName}, ${cursor.completeStringRepr()}',
);
}
bool _isClassDeclaration(clang_types.CXCursor cursor) {
// It's a class declaration if it has no children other than ObjCClassRef.
var result = true;
cursor.visitChildrenMayBreak((child) {
if (child.kind == clang_types.CXCursorKind.CXCursor_ObjCClassRef) {
return true;
}
result = false;
return false;
});
return result;
}
void _parseSuperType(
Context context,
clang_types.CXCursor cursor,
ObjCInterface itf,
) {
final superType = cursor.type().toCodeGenType(context);
context.logger.fine(
' > Super type: '
'$superType ${cursor.completeStringRepr()}',
);
if (superType is ObjCInterface) {
itf.superType = superType;
superType.subtypes.add(itf);
} else {
context.logger.severe(
'Super type of $itf is $superType, which is not a valid interface.',
);
}
}
(ObjCMethod?, ObjCMethod?) parseObjCProperty(
Context context,
clang_types.CXCursor cursor,
Declaration decl,
Declarations filters,
) {
final fieldName = cursor.spelling();
final fieldType = cursor.type().toCodeGenType(context);
final apiAvailability = ApiAvailability.fromCursor(cursor, context);
if (apiAvailability.availability == Availability.none) {
context.logger.info(
'Omitting deprecated property ${decl.originalName}.$fieldName',
);
return (null, null);
}
if (fieldType.isIncompleteCompound) {
context.logger.warning(
'Property "$fieldName" in instance "${decl.originalName}" '
'has incomplete type: $fieldType.',
);
return (null, null);
}
final dartDoc = getCursorDocComment(
context,
cursor,
availability: apiAvailability.dartDoc,
);
final propertyAttributes = clang.clang_Cursor_getObjCPropertyAttributes(
cursor,
0,
);
final isClassMethod =
propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_class >
0;
final isReadOnly =
propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly >
0;
final isOptionalMethod = clang.clang_Cursor_isObjCOptional(cursor) != 0;
context.logger.fine(
' > Property: '
'$fieldType $fieldName ${cursor.completeStringRepr()}',
);
final getterName = clang
.clang_Cursor_getObjCPropertyGetterName(cursor)
.toStringAndDispose();
final getter = ObjCMethod(
context: context,
originalName: getterName,
name: filters.renameMember(decl, getterName),
dartDoc: dartDoc ?? getterName,
kind: ObjCMethodKind.propertyGetter,
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: fieldType,
params: const [],
family: null,
apiAvailability: apiAvailability,
ownershipAttribute: null,
consumesSelfAttribute: false,
);
ObjCMethod? setter;
if (!isReadOnly) {
final setterName = clang
.clang_Cursor_getObjCPropertySetterName(cursor)
.toStringAndDispose();
setter = ObjCMethod.withSymbol(
context: context,
originalName: setterName,
symbol: getter.symbol,
protocolMethodName: setterName,
dartDoc: dartDoc ?? setterName,
kind: ObjCMethodKind.propertySetter,
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: voidType,
params: [Parameter(name: 'value', type: fieldType, objCConsumed: false)],
family: null,
apiAvailability: apiAvailability,
ownershipAttribute: null,
consumesSelfAttribute: false,
);
}
return (getter, setter);
}
ObjCMethod? parseObjCMethod(
Context context,
clang_types.CXCursor cursor,
Declaration itfDecl,
Declarations filters,
) {
final logger = context.logger;
final methodName = cursor.spelling();
final isClassMethod =
cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
final isOptionalMethod = clang.clang_Cursor_isObjCOptional(cursor) != 0;
final returnType = clang
.clang_getCursorResultType(cursor)
.toCodeGenType(context);
if (returnType.isIncompleteCompound) {
logger.warning(
'Method "$methodName" in instance '
'"${itfDecl.originalName}" has incomplete '
'return type: $returnType.',
);
return null;
}
final apiAvailability = ApiAvailability.fromCursor(cursor, context);
if (apiAvailability.availability == Availability.none) {
logger.info(
'Omitting deprecated method ${itfDecl.originalName}.$methodName',
);
return null;
}
logger.fine(
' > ${isClassMethod ? 'Class' : 'Instance'} method: '
'$methodName ${cursor.completeStringRepr()}',
);
final params = <Parameter>[];
ObjCMethodOwnership? ownershipAttribute;
var consumesSelfAttribute = false;
var hasError = false;
cursor.visitChildren((child) {
switch (child.kind) {
case clang_types.CXCursorKind.CXCursor_ParmDecl:
final p = _parseMethodParam(
context,
child,
itfDecl.originalName,
methodName,
);
if (p == null) {
hasError = true;
} else {
params.add(p);
}
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsRetained:
ownershipAttribute = ObjCMethodOwnership.retained;
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsNotRetained:
ownershipAttribute = ObjCMethodOwnership.notRetained;
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsAutoreleased:
ownershipAttribute = ObjCMethodOwnership.autoreleased;
break;
case clang_types.CXCursorKind.CXCursor_NSConsumesSelf:
consumesSelfAttribute = true;
break;
default:
}
});
if (hasError) return null;
return ObjCMethod(
context: context,
originalName: methodName,
name: filters.renameMember(itfDecl, methodName),
dartDoc: getCursorDocComment(
context,
cursor,
fallbackComment: methodName,
availability: apiAvailability.dartDoc,
),
kind: ObjCMethodKind.method,
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: returnType,
params: params,
family: ObjCMethodFamily.parse(methodName),
apiAvailability: apiAvailability,
ownershipAttribute: ownershipAttribute,
consumesSelfAttribute: consumesSelfAttribute,
);
}
Parameter? _parseMethodParam(
Context context,
clang_types.CXCursor cursor,
String itfName,
String methodName,
) {
final logger = context.logger;
final name = cursor.spelling();
final cursorType = cursor.type();
// Ignore methods with variadic args.
// TODO(https://github.com/dart-lang/native/issues/1192): Remove this.
if (cursorType.kind == clang_types.CXTypeKind.CXType_Elaborated &&
cursorType.spelling() == 'va_list') {
logger.warning(
'Method "$methodName" in instance '
'"$itfName" has variadic args, which are not currently supported',
);
return null;
}
final type = cursorType.toCodeGenType(context);
if (type.isIncompleteCompound) {
logger.warning(
'Method "$methodName" in instance '
'"$itfName" has incomplete parameter type: $type.',
);
return null;
}
logger.fine(
' >> Parameter: $type $name ${cursor.completeStringRepr()}',
);
final consumed = cursor.hasChildWithKind(
clang_types.CXCursorKind.CXCursor_NSConsumed,
);
return Parameter(name: name, type: type, objCConsumed: consumed);
}