[ffigen] Separate getDartType and getUserType (#623)

* Separate getDartType and getUserType

* Fix analysis

* Document instancetype special casing

* s/getDartType/getFfiDartType

* s/getUserType/getDartType
diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart
index db75582..c3eb55e 100644
--- a/pkgs/ffigen/lib/src/code_generator/compound.dart
+++ b/pkgs/ffigen/lib/src/code_generator/compound.dart
@@ -120,7 +120,7 @@
     /// Marking type names because dart doesn't allow class member to have the
     /// same name as a type name used internally.
     for (final m in members) {
-      localUniqueNamer.markUsed(m.type.getDartType(w));
+      localUniqueNamer.markUsed(m.type.getFfiDartType(w));
     }
 
     /// Write @Packed(X) annotation if struct is packed.
@@ -148,7 +148,7 @@
         if (!sameDartAndCType(m.type, w)) {
           s.write('$depth@${m.type.getCType(w)}()\n');
         }
-        s.write('${depth}external ${m.type.getDartType(w)} ${m.name};\n\n');
+        s.write('${depth}external ${m.type.getFfiDartType(w)} ${m.name};\n\n');
       }
     }
     s.write('}\n\n');
diff --git a/pkgs/ffigen/lib/src/code_generator/enum_class.dart b/pkgs/ffigen/lib/src/code_generator/enum_class.dart
index 081c864..70d142b 100644
--- a/pkgs/ffigen/lib/src/code_generator/enum_class.dart
+++ b/pkgs/ffigen/lib/src/code_generator/enum_class.dart
@@ -84,7 +84,7 @@
   String getCType(Writer w) => nativeType.getCType(w);
 
   @override
-  String getDartType(Writer w) => nativeType.getDartType(w);
+  String getFfiDartType(Writer w) => nativeType.getFfiDartType(w);
 
   @override
   String? getDefaultValue(Writer w, String nativeLib) => '0';
diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart
index f8d32e2..f8a8177 100644
--- a/pkgs/ffigen/lib/src/code_generator/func.dart
+++ b/pkgs/ffigen/lib/src/code_generator/func.dart
@@ -120,7 +120,7 @@
         : functionType.getCType(w, writeArgumentNames: false);
     final dartType = exposeFunctionTypedefs
         ? _exposedDartFunctionTypealias!.name
-        : functionType.getDartType(w, writeArgumentNames: false);
+        : functionType.getFfiDartType(w, writeArgumentNames: false);
 
     if (ffiNativeConfig.enabled) {
       final assetString = ffiNativeConfig.asset != null
@@ -131,17 +131,17 @@
           "@${w.ffiLibraryPrefix}.Native<$cType>(symbol: '$originalName'$assetString$isLeafString)\n");
 
       s.write(
-          'external ${functionType.returnType.getDartType(w)} $enclosingFuncName(\n');
+          'external ${functionType.returnType.getFfiDartType(w)} $enclosingFuncName(\n');
       for (final p in functionType.dartTypeParameters) {
-        s.write('  ${p.type.getDartType(w)} ${p.name},\n');
+        s.write('  ${p.type.getFfiDartType(w)} ${p.name},\n');
       }
       s.write(');\n\n');
     } else {
       // Write enclosing function.
       s.write(
-          '${functionType.returnType.getDartType(w)} $enclosingFuncName(\n');
+          '${functionType.returnType.getFfiDartType(w)} $enclosingFuncName(\n');
       for (final p in functionType.dartTypeParameters) {
-        s.write('  ${p.type.getDartType(w)} ${p.name},\n');
+        s.write('  ${p.type.getFfiDartType(w)} ${p.name},\n');
       }
       s.write(') {\n');
       s.write('return $funcVarName');
diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart
index 675c85b..3ee17d5 100644
--- a/pkgs/ffigen/lib/src/code_generator/func_type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart
@@ -65,16 +65,16 @@
   }
 
   @override
-  String getDartType(Writer w, {bool writeArgumentNames = true}) {
+  String getFfiDartType(Writer w, {bool writeArgumentNames = true}) {
     final sb = StringBuffer();
 
     // Write return Type.
-    sb.write(returnType.getDartType(w));
+    sb.write(returnType.getFfiDartType(w));
 
     // Write Function.
     sb.write(' Function(');
     sb.write(dartTypeParameters.map<String>((p) {
-      return '${p.type.getDartType(w)} ${writeArgumentNames ? p.name : ""}';
+      return '${p.type.getFfiDartType(w)} ${writeArgumentNames ? p.name : ""}';
     }).join(', '));
     sb.write(')');
 
@@ -137,7 +137,7 @@
       '${w.ffiLibraryPrefix}.NativeFunction<${_type.getCType(w)}>';
 
   @override
-  String getDartType(Writer w) => getCType(w);
+  String getFfiDartType(Writer w) => getCType(w);
 
   @override
   String toString() => 'NativeFunction<${_type.toString()}>';
diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart
index 579d8dd..5f2e639 100644
--- a/pkgs/ffigen/lib/src/code_generator/global.dart
+++ b/pkgs/ffigen/lib/src/code_generator/global.dart
@@ -45,7 +45,7 @@
       s.write(makeDartDoc(dartDoc!));
     }
     final pointerName = w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName');
-    final dartType = type.getDartType(w);
+    final dartType = type.getFfiDartType(w);
     final cType = type.getCType(w);
 
     s.write(
diff --git a/pkgs/ffigen/lib/src/code_generator/handle.dart b/pkgs/ffigen/lib/src/code_generator/handle.dart
index a1b2d47..c3817f2 100644
--- a/pkgs/ffigen/lib/src/code_generator/handle.dart
+++ b/pkgs/ffigen/lib/src/code_generator/handle.dart
@@ -16,7 +16,7 @@
   String getCType(Writer w) => '${w.ffiLibraryPrefix}.Handle';
 
   @override
-  String getDartType(Writer w) => 'Object';
+  String getFfiDartType(Writer w) => 'Object';
 
   @override
   String toString() => 'Handle';
diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart
index 93af5ad..f75a267 100644
--- a/pkgs/ffigen/lib/src/code_generator/imports.dart
+++ b/pkgs/ffigen/lib/src/code_generator/imports.dart
@@ -40,7 +40,7 @@
   }
 
   @override
-  String getDartType(Writer w) => cType == dartType ? getCType(w) : dartType;
+  String getFfiDartType(Writer w) => cType == dartType ? getCType(w) : dartType;
 
   @override
   String toString() => '${libraryImport.name}.$cType';
@@ -62,7 +62,7 @@
   String getCType(Writer w) => cType;
 
   @override
-  String getDartType(Writer w) => dartType;
+  String getFfiDartType(Writer w) => dartType;
 
   @override
   String toString() => cType;
diff --git a/pkgs/ffigen/lib/src/code_generator/native_type.dart b/pkgs/ffigen/lib/src/code_generator/native_type.dart
index dfb43a5..19425ba 100644
--- a/pkgs/ffigen/lib/src/code_generator/native_type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/native_type.dart
@@ -54,7 +54,7 @@
   String getCType(Writer w) => '${w.ffiLibraryPrefix}.$_cType';
 
   @override
-  String getDartType(Writer w) => _dartType;
+  String getFfiDartType(Writer w) => _dartType;
 
   @override
   String toString() => _cType;
@@ -67,7 +67,6 @@
 }
 
 class BooleanType extends NativeType {
-  // Booleans are treated as uint8.
   const BooleanType._() : super._('Bool', 'bool', 'false');
   static const _boolean = BooleanType._();
   factory BooleanType() => _boolean;
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
index 7bbb4c0..4c82a91 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
@@ -82,15 +82,15 @@
         '${w.ffiLibraryPrefix}.NativeCallable<${trampFuncType.getCType(w)}>';
 
     // Write the function pointer based trampoline function.
-    s.write(returnType.getDartType(w));
+    s.write(returnType.getFfiDartType(w));
     s.write(' $funcPtrTrampoline(${blockPtr.getCType(w)} block');
     for (int i = 0; i < params.length; ++i) {
-      s.write(', ${params[i].type.getDartType(w)} ${params[i].name}');
+      s.write(', ${params[i].type.getFfiDartType(w)} ${params[i].name}');
     }
     s.write(') {\n');
     s.write('  ${isVoid ? '' : 'return '}block.ref.target.cast<'
-        '${natFnType.getDartType(w)}>().asFunction<'
-        '${funcType.getDartType(w)}>()(');
+        '${natFnType.getFfiDartType(w)}>().asFunction<'
+        '${funcType.getFfiDartType(w)}>()(');
     for (int i = 0; i < params.length; ++i) {
       s.write('${i == 0 ? '' : ', '}${params[i].name}');
     }
@@ -109,17 +109,17 @@
 ''');
 
     // Write the closure based trampoline function.
-    s.write(returnType.getDartType(w));
+    s.write(returnType.getFfiDartType(w));
     s.write(' $closureTrampoline(${blockPtr.getCType(w)} block');
     for (int i = 0; i < params.length; ++i) {
-      s.write(', ${params[i].type.getDartType(w)} ${params[i].name}');
+      s.write(', ${params[i].type.getFfiDartType(w)} ${params[i].name}');
     }
     s.write(') {\n');
     s.write('  ${isVoid ? '' : 'return '}');
     s.write('($closureRegistry[block.ref.target.address]');
-    s.write(' as ${returnType.getDartType(w)} Function(');
+    s.write(' as ${returnType.getFfiDartType(w)} Function(');
     for (int i = 0; i < params.length; ++i) {
-      s.write('${i == 0 ? '' : ', '}${params[i].type.getDartType(w)}');
+      s.write('${i == 0 ? '' : ', '}${params[i].type.getFfiDartType(w)}');
     }
     s.write('))');
     s.write('(');
@@ -154,7 +154,7 @@
   /// This block must be invoked by native code running on the same thread as
   /// the isolate that registered it. Invoking the block on the wrong thread
   /// will result in a crash.
-  $name.fromFunction(${w.className} lib, ${funcType.getDartType(w)} fn) :
+  $name.fromFunction(${w.className} lib, ${funcType.getFfiDartType(w)} fn) :
       this._(lib.${builtInFunctions.newBlock.name}(
           _dartFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction<
               ${trampFuncType.getCType(w)}>($closureTrampoline
@@ -175,7 +175,7 @@
   ///
   /// Note that unlike the default behavior of NativeCallable.listener, listener
   /// blocks do not keep the isolate alive.
-  $name.listener(${w.className} lib, ${funcType.getDartType(w)} fn) :
+  $name.listener(${w.className} lib, ${funcType.getFfiDartType(w)} fn) :
       this._(lib.${builtInFunctions.newBlock.name}(
           (_dartFuncListenerTrampoline ??= $nativeCallableType.listener($closureTrampoline
                   $exceptionalReturn)..keepIsolateAlive = false).nativeFunction.cast(),
@@ -186,15 +186,15 @@
     }
 
     // Call method.
-    s.write('  ${returnType.getDartType(w)} call(');
+    s.write('  ${returnType.getFfiDartType(w)} call(');
     for (int i = 0; i < params.length; ++i) {
-      s.write('${i == 0 ? '' : ', '}${params[i].type.getDartType(w)}');
+      s.write('${i == 0 ? '' : ', '}${params[i].type.getFfiDartType(w)}');
       s.write(' ${params[i].name}');
     }
     s.write(''') {
     ${isVoid ? '' : 'return '}_id.ref.invoke.cast<
         ${natTrampFnType.getCType(w)}>().asFunction<
-            ${trampFuncType.getDartType(w)}>()(_id''');
+            ${trampFuncType.getFfiDartType(w)}>()(_id''');
     for (int i = 0; i < params.length; ++i) {
       s.write(', ${params[i].name}');
     }
@@ -223,5 +223,8 @@
       PointerType(builtInFunctions.blockStruct).getCType(w);
 
   @override
+  String getDartType(Writer w) => name;
+
+  @override
   String toString() => '($returnType (^)(${argTypes.join(', ')}))';
 }
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 782d75c..12ebd14 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -5,7 +5,6 @@
 import 'package:ffigen/src/code_generator.dart';
 import 'package:logging/logging.dart';
 
-import '../strings.dart' as strings;
 import 'binding_string.dart';
 import 'utils.dart';
 import 'writer.dart';
@@ -281,7 +280,7 @@
       if (m.isClass &&
           !_excludedNSObjectClassMethods.contains(m.originalName)) {
         addMethod(m);
-      } else if (_isInstanceType(m.returnType)) {
+      } else if (m.returnType is ObjCInstanceType) {
         addMethod(m);
       }
     }
@@ -355,13 +354,8 @@
   @override
   String getCType(Writer w) => PointerType(objCObjectType).getCType(w);
 
-  bool _isObject(Type type) =>
-      type is PointerType && type.child == objCObjectType;
-
-  bool _isInstanceType(Type type) =>
-      type is Typealias &&
-      type.originalName == strings.objcInstanceType &&
-      _isObject(type.type);
+  @override
+  String getDartType(Writer w) => name;
 
   // Utils for converting between the internal types passed to native code, and
   // the external types visible to the user. For example, ObjCInterfaces are
@@ -370,15 +364,11 @@
   bool _needsConverting(Type type) =>
       type is ObjCInterface ||
       type is ObjCBlock ||
-      _isObject(type) ||
-      _isInstanceType(type);
+      type is ObjCObjectPointer ||
+      type is ObjCInstanceType;
 
   String _getConvertedType(Type type, Writer w, String enclosingClass) {
-    if (type is BooleanType) return 'bool';
-    if (type is ObjCInterface) return type.name;
-    if (type is ObjCBlock) return type.name;
-    if (_isObject(type)) return 'NSObject';
-    if (_isInstanceType(type)) return enclosingClass;
+    if (type is ObjCInstanceType) return enclosingClass;
     return type.getDartType(w);
   }
 
@@ -393,8 +383,8 @@
 
   String _doArgConversion(ObjCMethodParam arg) {
     if (arg.type is ObjCInterface ||
-        _isObject(arg.type) ||
-        _isInstanceType(arg.type) ||
+        arg.type is ObjCObjectPointer ||
+        arg.type is ObjCInstanceType ||
         arg.type is ObjCBlock) {
       if (arg.isNullable) {
         return '${arg.name}?._id ?? ffi.nullptr';
@@ -415,10 +405,10 @@
     if (type is ObjCBlock) {
       return '$prefix${type.name}._($value, $library)';
     }
-    if (_isObject(type)) {
+    if (type is ObjCObjectPointer) {
       return '${prefix}NSObject._($value, $library, $ownerFlags)';
     }
-    if (_isInstanceType(type)) {
+    if (type is ObjCInstanceType) {
       return '$prefix$enclosingClass._($value, $library, $ownerFlags)';
     }
     return prefix + value;
diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart
index cacb5b2..f99ee53 100644
--- a/pkgs/ffigen/lib/src/code_generator/pointer.dart
+++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart
@@ -9,7 +9,15 @@
 /// Represents a pointer.
 class PointerType extends Type {
   final Type child;
-  PointerType(this.child);
+
+  PointerType._(this.child);
+
+  factory PointerType(Type child) {
+    if (child == objCObjectType) {
+      return ObjCObjectPointer();
+    }
+    return PointerType._(child);
+  }
 
   @override
   void addDependencies(Set<Binding> dependencies) {
@@ -33,7 +41,7 @@
 /// Represents a constant array, which has a fixed size.
 class ConstantArray extends PointerType {
   final int length;
-  ConstantArray(this.length, Type child) : super(child);
+  ConstantArray(this.length, Type child) : super._(child);
 
   @override
   Type get baseArrayType => child.baseArrayType;
@@ -50,7 +58,7 @@
 
 /// Represents an incomplete array, which has an unknown size.
 class IncompleteArray extends PointerType {
-  IncompleteArray(Type child) : super(child);
+  IncompleteArray(Type child) : super._(child);
 
   @override
   Type get baseArrayType => child.baseArrayType;
@@ -61,3 +69,14 @@
   @override
   String cacheKey() => '${child.cacheKey()}[]';
 }
+
+/// A pointer to an NSObject.
+class ObjCObjectPointer extends PointerType {
+  factory ObjCObjectPointer() => _inst;
+
+  static final _inst = ObjCObjectPointer._();
+  ObjCObjectPointer._() : super._(objCObjectType);
+
+  @override
+  String getDartType(Writer w) => 'NSObject';
+}
diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart
index 8468eca..1a69b60 100644
--- a/pkgs/ffigen/lib/src/code_generator/type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/type.dart
@@ -39,9 +39,14 @@
   /// passed to native code.
   String getCType(Writer w) => throw 'No mapping for type: $this';
 
-  /// Returns the Dart type of the Type. This is the user visible type that is
-  /// passed to Dart code.
-  String getDartType(Writer w) => getCType(w);
+  /// Returns the Dart type of the Type. This is the type that is passed from
+  /// FFI to Dart code.
+  String getFfiDartType(Writer w) => getCType(w);
+
+  /// Returns the user type of the Type. This is the type that is presented to
+  /// users by the ffigened API to users. For C bindings this is always the same
+  /// as getFfiDartType. For ObjC bindings this refers to the wrapper object.
+  String getDartType(Writer w) => getFfiDartType(w);
 
   /// Returns the string representation of the Type, for debugging purposes
   /// only. This string should not be printed as generated code.
@@ -62,7 +67,7 @@
 }
 
 /// Function to check if the dart and C type string are same.
-bool sameDartAndCType(Type t, Writer w) => t.getCType(w) == t.getDartType(w);
+bool sameDartAndCType(Type t, Writer w) => t.getCType(w) == t.getFfiDartType(w);
 
 /// Base class for all Type bindings.
 ///
@@ -97,7 +102,10 @@
   bool get isIncompleteCompound => false;
 
   @override
-  String getDartType(Writer w) => getCType(w);
+  String getFfiDartType(Writer w) => getCType(w);
+
+  @override
+  String getDartType(Writer w) => getFfiDartType(w);
 
   @override
   String toString() => originalName;
diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart
index c7d95ca..469a40b 100644
--- a/pkgs/ffigen/lib/src/code_generator/typealias.dart
+++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart
@@ -4,6 +4,7 @@
 
 import 'package:ffigen/src/code_generator.dart';
 
+import '../strings.dart' as strings;
 import 'binding_string.dart';
 import 'utils.dart';
 import 'writer.dart';
@@ -40,6 +41,18 @@
         isInternal: isInternal,
       )));
     }
+    if ((originalName ?? name) == strings.objcInstanceType &&
+        type is ObjCObjectPointer) {
+      return ObjCInstanceType._(
+        usr: usr,
+        originalName: originalName,
+        dartDoc: dartDoc,
+        name: name,
+        type: type,
+        useDartType: useDartType,
+        isInternal: isInternal,
+      );
+    }
     return Typealias._(
       usr: usr,
       originalName: originalName,
@@ -90,7 +103,7 @@
       sb.write(makeDartDoc(dartDoc!));
     }
     sb.write('typedef $name = ');
-    sb.write('${_useDartType ? type.getDartType(w) : type.getCType(w)};\n');
+    sb.write('${_useDartType ? type.getFfiDartType(w) : type.getCType(w)};\n');
     return BindingString(
         type: BindingStringType.typeDef, string: sb.toString());
   }
@@ -105,13 +118,13 @@
   String getCType(Writer w) => name;
 
   @override
-  String getDartType(Writer w) {
+  String getFfiDartType(Writer w) {
     // Typealias cannot be used by name in Dart types unless both the C and Dart
     // type of the underlying types are same.
     if (sameDartAndCType(type, w)) {
       return name;
     } else {
-      return type.getDartType(w);
+      return type.getFfiDartType(w);
     }
   }
 
@@ -122,3 +135,32 @@
   String? getDefaultValue(Writer w, String nativeLib) =>
       type.getDefaultValue(w, nativeLib);
 }
+
+/// Objective C's instancetype.
+///
+/// This is an alias for an NSObject* that is special cased in code generation.
+/// It's only valid as the return type of a method, and always appears as the
+/// enclosing class's type, even in inherited methods.
+class ObjCInstanceType extends Typealias {
+  ObjCInstanceType._({
+    String? usr,
+    String? originalName,
+    String? dartDoc,
+    required String name,
+    required Type type,
+
+    /// If true, the binding string uses Dart type instead of C type.
+    ///
+    /// E.g if C type is ffi.Void func(ffi.Int32), Dart type is void func(int).
+    bool useDartType = false,
+    bool isInternal = false,
+  }) : super._(
+          usr: usr,
+          originalName: originalName,
+          dartDoc: dartDoc,
+          name: name,
+          type: type,
+          useDartType: useDartType,
+          isInternal: isInternal,
+        );
+}