Skip to content
This repository has been archived by the owner on Jan 28, 2024. It is now read-only.

Commit

Permalink
Fix #334
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe committed Sep 26, 2023
1 parent 6746b58 commit f12d472
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 67 deletions.
1 change: 1 addition & 0 deletions lib/src/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export 'code_generator/native_type.dart';
export 'code_generator/objc_block.dart';
export 'code_generator/objc_built_in_functions.dart';
export 'code_generator/objc_interface.dart';
export 'code_generator/objc_nullable.dart';
export 'code_generator/pointer.dart';
export 'code_generator/struct.dart';
export 'code_generator/type.dart';
Expand Down
61 changes: 25 additions & 36 deletions lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,8 @@ class ObjCInterface extends BindingType {
if (isStatic) {
stringParams.add('${w.className} _lib');
}
stringParams.addAll(params.map((p) =>
(_getConvertedType(p.type, w, name) +
(p.isNullable ? "? " : " ") +
p.name)));
stringParams.addAll(
params.map((p) => '${_getConvertedType(p.type, w, name)} ${p.name}'));
return '(${stringParams.join(", ")})';
}

Expand Down Expand Up @@ -146,8 +144,7 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
s.write(' ');
if (isStatic) {
s.write('static ');
s.write(
_getConvertedReturnType(returnType, w, name, m.isNullableReturn));
s.write(_getConvertedType(returnType, w, name));

switch (m.kind) {
case ObjCMethodKind.method:
Expand All @@ -173,14 +170,12 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
switch (m.kind) {
case ObjCMethodKind.method:
// returnType methodName(...)
s.write(_getConvertedReturnType(
returnType, w, name, m.isNullableReturn));
s.write(_getConvertedType(returnType, w, name));
s.write(' $methodName');
s.write(paramsToString(params, isStatic: false));
break;
case ObjCMethodKind.propertyGetter:
s.write(_getConvertedReturnType(
returnType, w, name, m.isNullableReturn));
s.write(_getConvertedType(returnType, w, name));
if (isStret) {
// void getMethodName(Pointer<returnType> stret, NativeLibrary _lib)
s.write(' get');
Expand Down Expand Up @@ -219,8 +214,8 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
}
s.write(');\n');
if (convertReturn) {
final result = _doReturnConversion(returnType, '_ret', name, '_lib',
m.isNullableReturn, m.isOwnedReturn);
final result = _doReturnConversion(
returnType, '_ret', name, '_lib', m.isOwnedReturn);
s.write(' return $result;');
}

Expand Down Expand Up @@ -280,12 +275,16 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
if (m.isClass &&
!_excludedNSObjectClassMethods.contains(m.originalName)) {
addMethod(m);
} else if (m.returnType is ObjCInstanceType) {
} else if (_isInstanceType(m.returnType)) {
addMethod(m);
}
}
}

static bool _isInstanceType(Type type) =>
type is ObjCInstanceType ||
(type is ObjCNullable && type.child is ObjCInstanceType);

void addMethod(ObjCMethod method) {
final oldMethod = methods[method.originalName];
if (oldMethod != null) {
Expand Down Expand Up @@ -365,39 +364,33 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
type is ObjCInterface ||
type is ObjCBlock ||
type is ObjCObjectPointer ||
type is ObjCInstanceType;
type is ObjCInstanceType ||
type is ObjCNullable;

String _getConvertedType(Type type, Writer w, String enclosingClass) {
if (type is ObjCInstanceType) return enclosingClass;
return type.getDartType(w);
}

String _getConvertedReturnType(
Type type, Writer w, String enclosingClass, bool isNullableReturn) {
final result = _getConvertedType(type, w, enclosingClass);
if (isNullableReturn) {
return "$result?";
}
return result;
}

String _doArgConversion(ObjCMethodParam arg) {
if (arg.type is ObjCInterface ||
if (arg.type is ObjCNullable) {
return '${arg.name}?._id ?? ffi.nullptr';
} else if (arg.type is ObjCInterface ||
arg.type is ObjCObjectPointer ||
arg.type is ObjCInstanceType ||
arg.type is ObjCBlock) {
if (arg.isNullable) {
return '${arg.name}?._id ?? ffi.nullptr';
} else {
return '${arg.name}._id';
}
return '${arg.name}._id';
}
return arg.name;
}

String _doReturnConversion(Type type, String value, String enclosingClass,
String library, bool isNullable, bool isOwnedReturn) {
final prefix = isNullable ? '$value.address == 0 ? null : ' : '';
String library, bool isOwnedReturn) {
var prefix = '';
if (type is ObjCNullable) {
prefix = '$value.address == 0 ? null : ';
type = type.child;
}
final ownerFlags = 'retain: ${!isOwnedReturn}, release: true';
if (type is ObjCInterface) {
return '$prefix${type.name}._($value, $library, $ownerFlags)';
Expand Down Expand Up @@ -433,7 +426,6 @@ class ObjCMethod {
final String originalName;
final ObjCProperty? property;
final Type returnType;
final bool isNullableReturn;
final List<ObjCMethodParam> params;
final ObjCMethodKind kind;
final bool isClass;
Expand All @@ -448,7 +440,6 @@ class ObjCMethod {
required this.kind,
required this.isClass,
required this.returnType,
this.isNullableReturn = false,
List<ObjCMethodParam>? params_,
}) : params = params_ ?? [];

Expand Down Expand Up @@ -488,7 +479,6 @@ class ObjCMethod {

bool sameAs(ObjCMethod other) {
if (originalName != other.originalName) return false;
if (isNullableReturn != other.isNullableReturn) return false;
if (kind != other.kind) return false;
if (isClass != other.isClass) return false;
// msgSend is deduped by signature, so this check covers the signature.
Expand All @@ -506,6 +496,5 @@ class ObjCMethod {
class ObjCMethodParam {
final Type type;
final String name;
final bool isNullable;
ObjCMethodParam(this.type, this.name, {this.isNullable = false});
ObjCMethodParam(this.type, this.name);
}
44 changes: 44 additions & 0 deletions lib/src/code_generator/objc_nullable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2023, 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';

/// An ObjC type annotated with nullable.
class ObjCNullable extends Type {
Type child;

ObjCNullable(this.child) {
assert(child is ObjCInterface ||
child is ObjCBlock ||
child is ObjCObjectPointer ||
child is ObjCInstanceType);
}

@override
void addDependencies(Set<Binding> dependencies) {
child.addDependencies(dependencies);
}

@override
Type get baseType => child.baseType;

@override
String getCType(Writer w) => child.getCType(w);

@override
String getFfiDartType(Writer w) => child.getFfiDartType(w);

@override
String getDartType(Writer w) => '${child.getDartType(w)}?';

@override
String toString() => '$child?';

@override
String cacheKey() => '${child.cacheKey()}?';
}
25 changes: 21 additions & 4 deletions lib/src/header_parser/clang_bindings/clang_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,23 @@ class Clang {
late final _clang_Type_getAlignOf =
_clang_Type_getAlignOfPtr.asFunction<int Function(CXType)>();

/// Return the type that was modified by this attributed type.
///
/// If the type is not an attributed type, an invalid type is returned.
CXType clang_Type_getModifiedType(
CXType T,
) {
return _clang_Type_getModifiedType(
T,
);
}

late final _clang_Type_getModifiedTypePtr =
_lookup<ffi.NativeFunction<CXType Function(CXType)>>(
'clang_Type_getModifiedType');
late final _clang_Type_getModifiedType =
_clang_Type_getModifiedTypePtr.asFunction<CXType Function(CXType)>();

/// Determine whether the given cursor represents an anonymous
/// tag or namespace
int clang_Cursor_isAnonymous(
Expand Down Expand Up @@ -2623,10 +2640,10 @@ abstract class CXChildVisitResult {
///
/// The visitor should return one of the \c CXChildVisitResult values
/// to direct clang_visitCursorChildren().
typedef CXCursorVisitor = ffi.Pointer<
ffi.NativeFunction<
ffi.Int32 Function(
CXCursor cursor, CXCursor parent, CXClientData client_data)>>;
typedef CXCursorVisitor
= ffi.Pointer<ffi.NativeFunction<CXCursorVisitor_function>>;
typedef CXCursorVisitor_function = ffi.Int32 Function(
CXCursor cursor, CXCursor parent, CXClientData client_data);

/// Opaque pointer representing client data that will be passed through
/// to various callbacks and visitors.
Expand Down
4 changes: 3 additions & 1 deletion lib/src/header_parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ List<Binding> parseToBindings() {
0,
clang_types.CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies |
clang_types.CXTranslationUnit_Flags
.CXTranslationUnit_DetailedPreprocessingRecord,
.CXTranslationUnit_DetailedPreprocessingRecord |
clang_types.CXTranslationUnit_Flags
.CXTranslationUnit_IncludeAttributedTypes,
);

if (tu == nullptr) {
Expand Down
23 changes: 2 additions & 21 deletions lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,6 @@ void _parseProperty(clang_types.CXCursor cursor) {
final isReadOnly = propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly >
0;
// TODO(#334): Use the nullable attribute to decide this.
final isNullable =
cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;

final property = ObjCProperty(fieldName);

Expand All @@ -193,7 +190,6 @@ void _parseProperty(clang_types.CXCursor cursor) {
kind: ObjCMethodKind.propertyGetter,
isClass: isClass,
returnType: fieldType,
isNullableReturn: isNullable,
);
itf.addMethod(getter);

Expand All @@ -209,7 +205,7 @@ void _parseProperty(clang_types.CXCursor cursor) {
isClass: isClass,
returnType: NativeType(SupportedNativeType.Void));
setter.params
.add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
.add(ObjCMethodParam(fieldType, 'value'));
itf.addMethod(setter);
}
}
Expand Down Expand Up @@ -264,22 +260,7 @@ int _parseMethodVisitor(clang_types.CXCursor cursor,
}

void _parseMethodParam(clang_types.CXCursor cursor) {
/*
TODO(#334): Change this to use:
clang.clang_Type_getNullability(cursor.type()) ==
clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
NOTE: This will only work with the
clang_types
.CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes
option set.
*/
final parsed = _methodStack.top;
final isNullable =
cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
final name = cursor.spelling();
final type = cursor.type().toCodeGenType();
if (type.isIncompleteCompound) {
Expand All @@ -291,7 +272,7 @@ void _parseMethodParam(clang_types.CXCursor cursor) {
}
_logger.fine(
' >> Parameter: $type $name ${cursor.completeStringRepr()}');
parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
parsed.method.params.add(ObjCMethodParam(type, name));
}

void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
Expand Down
17 changes: 13 additions & 4 deletions lib/src/header_parser/type_extractor/extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,31 @@ Type getCodeGenType(
case clang_types.CXTypeKind.CXType_FunctionNoProto:
// Primarily used for function types with zero arguments.
return _extractFromFunctionProto(cxtype, cursor: originalCursor);
case clang_types.CXTypeKind
.CXType_ConstantArray: // Primarily used for constant array in struct members.
case clang_types.CXTypeKind.CXType_ConstantArray:
// Primarily used for constant array in struct members.
final numElements = clang.clang_getNumElements(cxtype);
final elementType =
clang.clang_getArrayElementType(cxtype).toCodeGenType();
// Handle numElements being 0 as an incomplete array.
return numElements == 0
? IncompleteArray(elementType)
: ConstantArray(numElements, elementType);
case clang_types.CXTypeKind
.CXType_IncompleteArray: // Primarily used for incomplete array in function parameters.
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();
case clang_types.CXTypeKind.CXType_Attributed:
final innerType = getCodeGenType(
clang.clang_Type_getModifiedType(cxtype),
ignoreFilter: ignoreFilter,
originalCursor: originalCursor,
);
final isNullable = clang.clang_Type_getNullability(cxtype) ==
clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
return isNullable ? ObjCNullable(innerType) : innerType;
default:
var typeSpellKey =
clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
Expand Down
2 changes: 1 addition & 1 deletion test/native_objc_test/nullable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ void main() {
test('Null', () {
expect(NullableInterface.returnNil_(lib, true), null);
});
}, skip: "TODO(#334): enable this test");
});

group('Nullable arguments', () {
test('Not null', () {
Expand Down
1 change: 1 addition & 0 deletions tool/libclang_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,6 @@ functions:
- clang_Cursor_getObjCPropertyGetterName
- clang_Cursor_getObjCPropertySetterName
- clang_Type_getNullability
- clang_Type_getModifiedType
- clang_Location_isInSystemHeader
- clang_getClangVersion

0 comments on commit f12d472

Please sign in to comment.