[ffigen] Skip methods that have incomplete types (#412)

* Skip methods that have incomplete types

Also change how method return types are parsed

* Fix overly generic method return types

* Fix tests
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 04eb6e5..b33e97b 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -125,7 +125,7 @@
     for (final m in methods.values) {
       final methodName = m._getDartMethodName(uniqueNamer);
       final isStatic = m.isClass;
-      final returnType = m.returnType!;
+      final returnType = m.returnType;
 
       // The method declaration.
       if (m.dartDoc != null) {
@@ -397,7 +397,7 @@
   final String? dartDoc;
   final String originalName;
   final ObjCProperty? property;
-  Type? returnType;
+  final Type returnType;
   final bool isNullableReturn;
   final List<ObjCMethodParam> params;
   final ObjCMethodKind kind;
@@ -412,7 +412,7 @@
     this.dartDoc,
     required this.kind,
     required this.isClass,
-    this.returnType,
+    required this.returnType,
     this.isNullableReturn = false,
     List<ObjCMethodParam>? params_,
   }) : params = params_ ?? [];
@@ -423,14 +423,13 @@
 
   void addDependencies(
       Set<Binding> dependencies, ObjCBuiltInFunctions builtInFunctions) {
-    returnType ??= NativeType(SupportedNativeType.Void);
-    returnType!.addDependencies(dependencies);
+    returnType.addDependencies(dependencies);
     for (final p in params) {
       p.type.addDependencies(dependencies);
     }
     selObject ??= builtInFunctions.getSelObject(originalName)
       ..addDependencies(dependencies);
-    msgSend ??= builtInFunctions.getMsgSendFunc(returnType!, params)
+    msgSend ??= builtInFunctions.getMsgSendFunc(returnType, params)
       ..addDependencies(dependencies);
   }
 
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
index 58108aa..e8755f5 100644
--- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -791,6 +791,23 @@
       _clang_Type_getObjCObjectBaseTypePtr
           .asFunction<CXType Function(CXType)>();
 
+  /// Retrieve the return type associated with a given cursor.
+  ///
+  /// This only returns a valid type if the cursor refers to a function or method.
+  CXType clang_getCursorResultType(
+    CXCursor C,
+  ) {
+    return _clang_getCursorResultType(
+      C,
+    );
+  }
+
+  late final _clang_getCursorResultTypePtr =
+      _lookup<ffi.NativeFunction<CXType Function(CXCursor)>>(
+          'clang_getCursorResultType');
+  late final _clang_getCursorResultType =
+      _clang_getCursorResultTypePtr.asFunction<CXType Function(CXCursor)>();
+
   /// Return the number of elements of an array or vector type.
   ///
   /// If a type is passed in that is not an array or vector type,
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
index bf9d038..f2e4fc9 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -178,8 +178,8 @@
         property: property,
         dartDoc: dartDoc,
         kind: ObjCMethodKind.propertySetter,
-        isClass: isClass);
-    setter.returnType = NativeType(SupportedNativeType.Void);
+        isClass: isClass,
+        returnType: NativeType(SupportedNativeType.Void));
     setter.params
         .add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
     itf.addMethod(setter);
@@ -190,11 +190,19 @@
   final methodName = cursor.spelling();
   final isClassMethod =
       cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
+  final returnType = clang.clang_getCursorResultType(cursor).toCodeGenType();
+  if (returnType.isIncompleteCompound) {
+    _logger.warning('Method "$methodName" in instance '
+        '"${_interfaceStack.top.interface.originalName}" has incomplete '
+        'return type: $returnType.');
+    return;
+  }
   final method = ObjCMethod(
     originalName: methodName,
     dartDoc: getCursorDocComment(cursor),
     kind: ObjCMethodKind.method,
     isClass: isClassMethod,
+    returnType: returnType,
   );
   final parsed = _ParsedObjCMethod(method);
   _logger.fine('       > ${isClassMethod ? 'Class' : 'Instance'} method: '
@@ -215,10 +223,6 @@
 int _parseMethodVisitor(clang_types.CXCursor cursor,
     clang_types.CXCursor parent, Pointer<Void> clientData) {
   switch (cursor.kind) {
-    case clang_types.CXCursorKind.CXCursor_TypeRef:
-    case clang_types.CXCursorKind.CXCursor_ObjCClassRef:
-      _parseMethodReturnType(cursor);
-      break;
     case clang_types.CXCursorKind.CXCursor_ParmDecl:
       _parseMethodParam(cursor);
       break;
@@ -230,22 +234,6 @@
   return clang_types.CXChildVisitResult.CXChildVisit_Continue;
 }
 
-void _parseMethodReturnType(clang_types.CXCursor cursor) {
-  final parsed = _methodStack.top;
-  if (parsed.method.returnType != null) {
-    parsed.hasError = true;
-    _logger.fine(
-        '           >> Extra return type: ${cursor.completeStringRepr()}');
-    _logger.warning('Method "${parsed.method.originalName}" in instance '
-        '"${_interfaceStack.top.interface.originalName}" has multiple return '
-        'types.');
-  } else {
-    parsed.method.returnType = cursor.type().toCodeGenType();
-    _logger.fine('           >> Return type: '
-        '${parsed.method.returnType} ${cursor.completeStringRepr()}');
-  }
-}
-
 void _parseMethodParam(clang_types.CXCursor cursor) {
   /*
   TODO(#334): Change this to use:
@@ -260,14 +248,21 @@
 
   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) {
+    parsed.hasError = true;
+    _logger.warning('Method "${parsed.method.originalName}" in instance '
+        '"${_interfaceStack.top.interface.originalName}" has incomplete '
+        'parameter type: $type.');
+    return;
+  }
   _logger.fine(
       '           >> Parameter: $type $name ${cursor.completeStringRepr()}');
-  _methodStack.top.method.params
-      .add(ObjCMethodParam(type, name, isNullable: isNullable));
+  parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
 }
 
 void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 1a69715..31420cd 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -45,6 +45,13 @@
   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:
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml b/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml
new file mode 100644
index 0000000..d26ff79
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml
@@ -0,0 +1,23 @@
+# 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.
+
+# =================== GENERATING TEST BINDINGS ==================
+#    dart run ffigen --config test/bad_method_test/config.yaml
+# ===============================================================
+
+name: NativeObjCLibrary
+description: 'Native Objective C test'
+language: objc
+output: 'test/native_objc_test/bad_method_test_bindings.dart'
+objc-interfaces:
+  include:
+    - 'BadMethodTestObject'
+functions:
+  exclude:
+    - '.*'
+headers:
+  entry-points:
+    - 'test/native_objc_test/bad_method_test.m'
+preamble: |
+  // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_test.dart b/pkgs/ffigen/test/native_objc_test/bad_method_test.dart
new file mode 100644
index 0000000..625cc2b
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_test.dart
@@ -0,0 +1,34 @@
+// 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.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'bad_method_test_bindings.dart';
+import 'util.dart';
+
+void main() {
+  late NativeObjCLibrary lib;
+  group('bad_method_test', () {
+    setUpAll(() {
+      logWarnings();
+      final dylib = File('test/native_objc_test/bad_method_test.dylib');
+      verifySetupFile(dylib);
+      lib = NativeObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
+      generateBindingsForCoverage('bad_method');
+    });
+
+    test("Test methods that weren't skipped", () {
+      final obj = BadMethodTestObject.new1(lib);
+      final structPtr = obj.incompletePointerReturn();
+      expect(structPtr.address, 1234);
+      expect(obj.incompletePointerParam_(structPtr), 1234);
+    });
+  });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_test.m b/pkgs/ffigen/test/native_objc_test/bad_method_test.m
new file mode 100644
index 0000000..132daf8
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_test.m
@@ -0,0 +1,29 @@
+// 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 <Foundation/NSObject.h>
+
+struct IncompleteStruct;
+
+@interface BadMethodTestObject : NSObject {
+}
+
+- (struct IncompleteStruct)incompleteReturn;  // Skipped.
+- (struct IncompleteStruct*)incompletePointerReturn;  // Not skipped.
+- (int64_t)incompleteParam:(struct IncompleteStruct)x;  // Skipped.
+- (int64_t)incompletePointerParam:(struct IncompleteStruct*)x;  // Not skipped.
+
+@end
+
+@implementation BadMethodTestObject
+
+- (struct IncompleteStruct*)incompletePointerReturn {
+  return (struct IncompleteStruct*)1234;
+}
+
+- (int64_t)incompletePointerParam:(struct IncompleteStruct*)x {
+  return (int64_t)x;
+}
+
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart
index 22d99c9..78c7427 100644
--- a/pkgs/ffigen/test/native_objc_test/setup.dart
+++ b/pkgs/ffigen/test/native_objc_test/setup.dart
@@ -47,6 +47,7 @@
 
 const testNames = [
   'automated_ref_count',
+  'bad_method',
   'block',
   'cast',
   'category',
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml
index ad0bfd0..3087cf8 100644
--- a/pkgs/ffigen/tool/libclang_config.yaml
+++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -90,6 +90,7 @@
     - clang_Cursor_getArgument
     - clang_getNumArgTypes
     - clang_getArgType
+    - clang_getCursorResultType
     - clang_getEnumConstantDeclValue
     - clang_equalRanges
     - clang_Cursor_getCommentRange