Fix ObjC `instancetype` inheritance issue. (#618)

* Fix #486

* changelog and format

* Fix nit
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b90e619..efc5af3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 - Fix invalid exceptional return value ObjCBlocks that return floats.
 - Fix return_of_invalid_type analysis error for ObjCBlocks.
 - Fix crash in ObjC methods and blocks that return structs by value.
+- Fix ObjC methods returning instancetype having the wrong type in sublasses.
 - Bump min SDK version to 3.2.0-114.0.dev.
 
 # 9.0.1
diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart
index bf4d887..782d75c 100644
--- a/lib/src/code_generator/objc_interface.dart
+++ b/lib/src/code_generator/objc_interface.dart
@@ -263,7 +263,7 @@
 
     if (superType != null) {
       superType!.addDependencies(dependencies);
-      _copyClassMethodsFromSuperType();
+      _copyMethodsFromSuperType();
     }
 
     for (final m in methods.values) {
@@ -271,13 +271,18 @@
     }
   }
 
-  void _copyClassMethodsFromSuperType() {
-    // Copy class methods from the super type, because Dart classes don't
-    // inherit static methods.
+  void _copyMethodsFromSuperType() {
+    // We need to copy certain methods from the super type:
+    //  - Class methods, because Dart classes don't inherit static methods.
+    //  - Methods that return instancetype, because the subclass's copy of the
+    //    method needs to return the subclass, not the super class.
+    //    Note: instancetype is only allowed as a return type, not an arg type.
     for (final m in superType!.methods.values) {
       if (m.isClass &&
           !_excludedNSObjectClassMethods.contains(m.originalName)) {
         addMethod(m);
+      } else if (_isInstanceType(m.returnType)) {
+        addMethod(m);
       }
     }
   }
diff --git a/test/native_objc_test/inherited_instancetype_config.yaml b/test/native_objc_test/inherited_instancetype_config.yaml
new file mode 100644
index 0000000..9261979
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_config.yaml
@@ -0,0 +1,13 @@
+name: InheritedInstancetypeTestObjCLibrary
+description: 'Regression tests for https://github.com/dart-lang/ffigen/issues/486'
+language: objc
+output: 'inherited_instancetype_bindings.dart'
+exclude-all-by-default: true
+objc-interfaces:
+  include:
+    - ChildClass
+headers:
+  entry-points:
+    - 'inherited_instancetype_test.m'
+preamble: |
+  // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/inherited_instancetype_test.dart b/test/native_objc_test/inherited_instancetype_test.dart
new file mode 100644
index 0000000..93f9355
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_test.dart
@@ -0,0 +1,59 @@
+// 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.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+// Regression tests for https://github.com/dart-lang/ffigen/issues/486.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'inherited_instancetype_bindings.dart';
+import 'util.dart';
+
+void main() {
+  late InheritedInstancetypeTestObjCLibrary lib;
+
+  group('inheritedInstancetype', () {
+    setUpAll(() {
+      logWarnings();
+      final dylib =
+          File('test/native_objc_test/inherited_instancetype_test.dylib');
+      verifySetupFile(dylib);
+      lib = InheritedInstancetypeTestObjCLibrary(
+          DynamicLibrary.open(dylib.absolute.path));
+      generateBindingsForCoverage('inherited_instancetype');
+    });
+
+    test('Ordinary init method', () {
+      final ChildClass child = ChildClass.alloc(lib).init();
+      expect(child.field, 123);
+      final ChildClass sameChild = child.getSelf();
+      sameChild.field = 456;
+      expect(child.field, 456);
+    });
+
+    test('Custom create method', () {
+      final ChildClass child = ChildClass.create(lib);
+      expect(child.field, 123);
+      final ChildClass sameChild = child.getSelf();
+      sameChild.field = 456;
+      expect(child.field, 456);
+    });
+
+    test('Polymorphism', () {
+      final ChildClass child = ChildClass.alloc(lib).init();
+      final BaseClass base = child;
+
+      // Calling base.getSelf() should still go through ChildClass.getSelf, so
+      // the result will have a compile time type of BaseClass, but a runtime
+      // type of ChildClass.
+      final BaseClass sameChild = base.getSelf();
+      expect(sameChild, isA<ChildClass>());
+    });
+  });
+}
diff --git a/test/native_objc_test/inherited_instancetype_test.m b/test/native_objc_test/inherited_instancetype_test.m
new file mode 100644
index 0000000..8f1b20b
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_test.m
@@ -0,0 +1,33 @@
+// 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 <Foundation/NSObject.h>
+
+@interface BaseClass : NSObject {}
++ (instancetype)create;
+- (instancetype)getSelf;
+@end
+
+@interface ChildClass : BaseClass {}
+@property int32_t field;
+@end
+
+@implementation BaseClass
++ (instancetype)create {
+  return [[[self class] alloc] init];
+}
+
+- (instancetype)getSelf {
+  return self;
+}
+@end
+
+@implementation ChildClass
+- (instancetype)init {
+  if (self = [super init]) {
+    self.field = 123;
+  }
+  return self;
+}
+@end