blob: 1590d73db8956606d0cb6e5c442109e4df719041 [file] [log] [blame]
// Copyright (c) 2020, 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.
/// Extracts code_gen Type from type.
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/header_parser/sub_parsers/typedefdecl_parser.dart';
import 'package:ffigen/src/strings.dart' as strings;
import 'package:logging/logging.dart';
import '../../config_provider/config_types.dart';
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../data.dart';
import '../sub_parsers/compounddecl_parser.dart';
import '../sub_parsers/enumdecl_parser.dart';
import '../sub_parsers/objc_block_parser.dart';
import '../sub_parsers/objcinterfacedecl_parser.dart';
import '../type_extractor/cxtypekindmap.dart';
import '../utils.dart';
final _logger = Logger('ffigen.header_parser.extractor');
const _padding = ' ';
/// Converts cxtype to a typestring code_generator can accept.
Type getCodeGenType(
clang_types.CXType cxtype, {
/// Option to ignore declaration filter (Useful in case of extracting
/// declarations when they are passed/returned by an included function.)
bool ignoreFilter = true,
/// Passed on if a value was marked as a pointer before this one.
bool pointerReference = false,
}) {
_logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}');
// Special case: Elaborated types just refer to another type.
if (cxtype.kind == clang_types.CXTypeKind.CXType_Elaborated) {
return getCodeGenType(clang.clang_Type_getNamedType(cxtype),
ignoreFilter: ignoreFilter, pointerReference: pointerReference);
}
// These basic Objective C types skip the cache, and are conditional on the
// language flag.
if (config.language == Language.objc) {
switch (cxtype.kind) {
case clang_types.CXTypeKind.CXType_ObjCObjectPointer:
final pt = clang.clang_getPointeeType(cxtype);
final s = getCodeGenType(pt,
ignoreFilter: ignoreFilter, pointerReference: true);
if (s is ObjCInterface) {
return s;
}
return PointerType(objCObjectType);
case clang_types.CXTypeKind.CXType_ObjCId:
case clang_types.CXTypeKind.CXType_ObjCTypeParam:
case clang_types.CXTypeKind.CXType_ObjCClass:
return PointerType(objCObjectType);
case clang_types.CXTypeKind.CXType_ObjCSel:
return PointerType(objCSelType);
case clang_types.CXTypeKind.CXType_BlockPointer:
return _getOrCreateBlockType(cxtype);
}
}
// If the type has a declaration cursor, then use the BindingsIndex to break
// any potential cycles, and dedupe the Type.
final cursor = clang.clang_getTypeDeclaration(cxtype);
if (cursor.kind != clang_types.CXCursorKind.CXCursor_NoDeclFound) {
final usr = cursor.usr();
var type = bindingsIndex.getSeenType(usr);
if (type == null) {
final result =
_createTypeFromCursor(cxtype, cursor, ignoreFilter, pointerReference);
type = result.type;
if (type == null) {
return UnimplementedType('${cxtype.kindSpelling()} not implemented');
}
if (result.addToCache) {
bindingsIndex.addTypeToSeen(usr, type);
}
}
_fillFromCursorIfNeeded(type, cursor, ignoreFilter, pointerReference);
return type;
}
// If the type doesn't have a declaration cursor, then it's a basic type such
// as int, or a simple derived type like a pointer, so doesn't need to be
// cached.
switch (cxtype.kind) {
case clang_types.CXTypeKind.CXType_Pointer:
final pt = clang.clang_getPointeeType(cxtype);
final s = getCodeGenType(pt, pointerReference: true);
// Replace Pointer<_Dart_Handle> with Handle.
if (config.useDartHandle &&
s is Compound &&
s.compoundType == CompoundType.struct &&
s.usr == strings.dartHandleUsr) {
return HandleType();
}
return PointerType(s);
case clang_types.CXTypeKind.CXType_FunctionProto:
// Primarily used for function pointers.
return _extractFromFunctionProto(cxtype);
case clang_types.CXTypeKind.CXType_FunctionNoProto:
// Primarily used for function types with zero arguments.
return _extractFromFunctionProto(cxtype);
case clang_types.CXTypeKind
.CXType_ConstantArray: // Primarily used for constant array in struct members.
return ConstantArray(
clang.clang_getNumElements(cxtype),
clang.clang_getArrayElementType(cxtype).toCodeGenType(),
);
case clang_types.CXTypeKind
.CXType_IncompleteArray: // Primarily used for incomplete array in function parameters.
return IncompleteArray(
clang.clang_getArrayElementType(cxtype).toCodeGenType(),
);
case clang_types.CXTypeKind.CXType_Bool:
return BooleanType();
default:
var typeSpellKey =
clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
if (typeSpellKey.startsWith('const ')) {
typeSpellKey = typeSpellKey.replaceFirst('const ', '');
}
if (config.nativeTypeMappings.containsKey(typeSpellKey)) {
_logger.fine(' Type $typeSpellKey mapped from type-map.');
return config.nativeTypeMappings[typeSpellKey]!;
} else if (cxTypeKindToImportedTypes.containsKey(typeSpellKey)) {
return cxTypeKindToImportedTypes[typeSpellKey]!;
} else {
_logger.fine('typedeclarationCursorVisitor: getCodeGenType: Type Not '
'Implemented, ${cxtype.completeStringRepr()}');
return UnimplementedType('${cxtype.kindSpelling()} not implemented');
}
}
}
Type _getOrCreateBlockType(clang_types.CXType cxtype) {
final block = parseObjCBlock(cxtype);
final key = block.usr;
final oldBlock = bindingsIndex.getSeenObjCBlock(key);
if (oldBlock != null) {
return oldBlock;
}
bindingsIndex.addObjCBlockToSeen(key, block);
return block;
}
class _CreateTypeFromCursorResult {
final Type? type;
// Flag that controls whether the type is added to the cache. It should not
// be added to the cache if it's just a fallback implementation, such as the
// int that is returned when an enum is excluded by the config. Later we might
// need to build the full enum type (eg if it's part of an included struct),
// and if we put the fallback int in the cache then the full enum will never
// be created.
final bool addToCache;
_CreateTypeFromCursorResult(this.type, {this.addToCache = true});
}
_CreateTypeFromCursorResult _createTypeFromCursor(clang_types.CXType cxtype,
clang_types.CXCursor cursor, bool ignoreFilter, bool pointerReference) {
switch (cxtype.kind) {
case clang_types.CXTypeKind.CXType_Typedef:
final spelling = clang.clang_getTypedefName(cxtype).toStringAndDispose();
if (config.language == Language.objc && spelling == strings.objcBOOL) {
// Objective C's BOOL type can be either bool or signed char, depending
// on the platform. We want to present a consistent API to the user, and
// those two types are ABI compatible, so just return bool regardless.
return _CreateTypeFromCursorResult(BooleanType());
}
if (config.typedefTypeMappings.containsKey(spelling)) {
_logger.fine(' Type $spelling mapped from type-map');
return _CreateTypeFromCursorResult(
config.typedefTypeMappings[spelling]!);
}
// Get name from supported typedef name if config allows.
if (config.useSupportedTypedefs) {
if (suportedTypedefToSuportedNativeType.containsKey(spelling)) {
_logger.fine(' Type Mapped from supported typedef');
return _CreateTypeFromCursorResult(
NativeType(suportedTypedefToSuportedNativeType[spelling]!));
} else if (supportedTypedefToImportedType.containsKey(spelling)) {
_logger.fine(' Type Mapped from supported typedef');
return _CreateTypeFromCursorResult(
supportedTypedefToImportedType[spelling]!);
}
}
final typealias = parseTypedefDeclaration(cursor,
ignoreFilter: ignoreFilter, pointerReference: pointerReference);
if (typealias != null) {
return _CreateTypeFromCursorResult(typealias);
} else {
// Use underlying type if typealias couldn't be created or if the user
// excluded this typedef.
final ct = clang.clang_getTypedefDeclUnderlyingType(cursor);
return _CreateTypeFromCursorResult(
getCodeGenType(ct, pointerReference: pointerReference),
addToCache: false);
}
case clang_types.CXTypeKind.CXType_Record:
return _CreateTypeFromCursorResult(
_extractfromRecord(cxtype, cursor, ignoreFilter, pointerReference));
case clang_types.CXTypeKind.CXType_Enum:
final enumClass = parseEnumDeclaration(
cursor,
ignoreFilter: ignoreFilter,
);
if (enumClass == null) {
// Handle anonymous enum declarations within another declaration.
return _CreateTypeFromCursorResult(EnumClass.nativeType,
addToCache: false);
} else {
return _CreateTypeFromCursorResult(enumClass);
}
case clang_types.CXTypeKind.CXType_ObjCInterface:
return _CreateTypeFromCursorResult(
parseObjCInterfaceDeclaration(cursor, ignoreFilter: ignoreFilter));
default:
return _CreateTypeFromCursorResult(
UnimplementedType('Unknown type: ${cxtype.completeStringRepr()}'),
addToCache: false);
}
}
void _fillFromCursorIfNeeded(Type? type, clang_types.CXCursor cursor,
bool ignoreFilter, bool pointerReference) {
if (type == null) return;
if (type is Compound) {
fillCompoundMembersIfNeeded(type, cursor,
ignoreFilter: ignoreFilter, pointerReference: pointerReference);
} else if (type is ObjCInterface) {
fillObjCInterfaceMethodsIfNeeded(type, cursor);
}
}
Type? _extractfromRecord(clang_types.CXType cxtype, clang_types.CXCursor cursor,
bool ignoreFilter, bool pointerReference) {
_logger.fine('${_padding}_extractfromRecord: ${cursor.completeStringRepr()}');
final cursorKind = clang.clang_getCursorKind(cursor);
if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl ||
cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) {
final declSpelling = cursor.spelling();
// Set includer functions according to compoundType.
final CompoundType compoundType;
final Map<String, ImportedType> compoundTypeMappings;
switch (cursorKind) {
case clang_types.CXCursorKind.CXCursor_StructDecl:
compoundType = CompoundType.struct;
compoundTypeMappings = config.structTypeMappings;
break;
case clang_types.CXCursorKind.CXCursor_UnionDecl:
compoundType = CompoundType.union;
compoundTypeMappings = config.unionTypeMappings;
break;
default:
throw Exception('Unhandled compound type cursorkind.');
}
// Also add a struct binding, if its unseen.
// TODO(23): Check if we should auto add compound declarations.
if (compoundTypeMappings.containsKey(declSpelling)) {
_logger.fine(' Type Mapped from type-map');
return compoundTypeMappings[declSpelling]!;
} else {
final struct = parseCompoundDeclaration(
cursor,
compoundType,
ignoreFilter: ignoreFilter,
pointerReference: pointerReference,
);
return struct;
}
}
_logger.fine('typedeclarationCursorVisitor: _extractfromRecord: '
'Not Implemented, ${cursor.completeStringRepr()}');
return UnimplementedType('${cxtype.kindSpelling()} not implemented');
}
// Used for function pointer arguments.
Type _extractFromFunctionProto(clang_types.CXType cxtype) {
final _parameters = <Parameter>[];
final totalArgs = clang.clang_getNumArgTypes(cxtype);
for (var i = 0; i < totalArgs; i++) {
final t = clang.clang_getArgType(cxtype, i);
final pt = t.toCodeGenType();
if (pt.isIncompleteCompound) {
return UnimplementedType(
'Incomplete Struct by value in function parameter.');
} else if (pt.baseType is UnimplementedType) {
return UnimplementedType('Function parameter has an unsupported type.');
}
_parameters.add(
Parameter(name: '', type: pt),
);
}
return NativeFunc(FunctionType(
parameters: _parameters,
returnType: clang.clang_getResultType(cxtype).toCodeGenType(),
));
}