[vm/ffi] Allow omitting native types for `@Native` functions

This change simplifies working with `@Native`-annotated functions by allowing the native type to be omitted when it can be inferred from the Dart function's signature. While this was previously supported for `@Native` fields, it now applies to functions as well.

Before this change, you needed to specify the native type explicitly:
```
@Native<Void Function(Pointer)>()
external void free(Pointer p);
```

After this change, the native type can now be omitted if it's clear from the Dart signature:
```
@Native()
external void free(Pointer p);
```

TEST=tests/ffi/native_assets/*

CoreLibraryReviewExempt: VM only
Closes: https://github.com/dart-lang/sdk/issues/54810
Change-Id: Ied5407fcd2f49d85284cb7817f0c8cad2a73626b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400840
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Moritz Sümmermann <mosum@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index b0cbf2f..5557089 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -6648,6 +6648,18 @@
 );
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeFfiNativeFunctionMissingType =
+    messageFfiNativeFunctionMissingType;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageFfiNativeFunctionMissingType = const MessageCode(
+  "FfiNativeFunctionMissingType",
+  analyzerCodes: <String>["NATIVE_FUNCTION_MISSING_TYPE"],
+  problemMessage:
+      r"""The native type of this function couldn't be inferred so it must be specified in the annotation.""",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 9460639..7f7b4a1 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1792,6 +1792,8 @@
   status: needsEvaluation
 FfiCode.NATIVE_FIELD_NOT_STATIC:
   status: needsEvaluation
+FfiCode.NATIVE_FUNCTION_MISSING_TYPE:
+  status: needsEvaluation
 FfiCode.NEGATIVE_VARIABLE_DIMENSION:
   status: noFix
 FfiCode.NON_CONSTANT_TYPE_ARGUMENT:
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
index 255d1f5..fce374c 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -387,6 +387,16 @@
     hasPublishedDocs: true,
   );
 
+  ///  No parameters
+  static const FfiCode NATIVE_FUNCTION_MISSING_TYPE = FfiCode(
+    'NATIVE_FUNCTION_MISSING_TYPE',
+    "The native type of this function couldn't be inferred so it must be "
+        "specified in the annotation.",
+    correctionMessage:
+        "Try adding a type parameter extending `NativeType` to the `@Native` "
+        "annotation.",
+  );
+
   ///  No parameters.
   static const FfiCode NEGATIVE_VARIABLE_DIMENSION = FfiCode(
     'NEGATIVE_VARIABLE_DIMENSION',
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index aea94ad..f55471a 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -632,6 +632,7 @@
   FfiCode.NATIVE_FIELD_INVALID_TYPE,
   FfiCode.NATIVE_FIELD_MISSING_TYPE,
   FfiCode.NATIVE_FIELD_NOT_STATIC,
+  FfiCode.NATIVE_FUNCTION_MISSING_TYPE,
   FfiCode.NEGATIVE_VARIABLE_DIMENSION,
   FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
   FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index e4a9a53..88b3aa2 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -110,6 +110,9 @@
   /// Subclass of `Struct` or `Union` we are currently visiting, or `null`.
   ClassDeclaration? compound;
 
+  /// The `Void` type from `dart:ffi`, or `null` if unresolved.
+  InterfaceType? ffiVoidType;
+
   /// Initialize a newly created verifier.
   FfiVerifier(this.typeSystem, this._errorReporter,
       {required this.strictCasts});
@@ -493,8 +496,36 @@
           );
         }
       } else {
-        if (declarationElement is MethodElement2 ||
-            declarationElement is TopLevelFunctionElement) {
+        if (declarationElement
+            case TopLevelFunctionElement() || MethodElement2()) {
+          declarationElement = declarationElement as ExecutableElement2;
+          var dartSignature = declarationElement.type;
+
+          if (declarationElement.isStatic && ffiSignature is DynamicType) {
+            // No type argument was given on the @Native annotation, so we try
+            // to infer the native type from the Dart signature.
+            if (dartSignature.returnType is VoidType) {
+              // The Dart signature has a `void` return type, so we create a new
+              // `FunctionType` with FFI's `Void` as the return type.
+              dartSignature = FunctionTypeImpl.v2(
+                typeParameters: dartSignature.typeParameters,
+                formalParameters: dartSignature.formalParameters,
+                returnType: ffiVoidType ??= annotationType.element3.library2
+                    .getClass2('Void')!
+                    .thisType,
+                nullabilitySuffix: dartSignature.nullabilitySuffix,
+              );
+            }
+            _checkFfiNativeFunction(
+              errorNode,
+              declarationElement,
+              dartSignature,
+              annotationValue,
+              formalParameters,
+            );
+            return;
+          }
+
           // Function annotated with something that isn't a function type.
           _errorReporter.atToken(
             errorNode,
@@ -512,9 +543,6 @@
           );
         }
       }
-
-      if (ffiSignature is FunctionType &&
-          declarationElement is ExecutableElement2) {}
     }
   }
 
@@ -683,11 +711,20 @@
       nullabilitySuffix: ffiSignature.nullabilitySuffix,
     );
     if (!_isValidFfiNativeFunctionType(nativeType)) {
-      _errorReporter.atToken(
-        errorToken,
-        FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
-        arguments: [nativeType, 'Native'],
-      );
+      var nativeTypeIsOmitted = (annotationValue.type! as InterfaceType)
+          .typeArguments[0] is DynamicType;
+      if (nativeTypeIsOmitted) {
+        _errorReporter.atToken(
+          errorToken,
+          FfiCode.NATIVE_FUNCTION_MISSING_TYPE,
+        );
+      } else {
+        _errorReporter.atToken(
+          errorToken,
+          FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+          arguments: [nativeType, 'Native'],
+        );
+      }
       return;
     }
     if (!_validateCompatibleFunctionTypes(
@@ -1707,15 +1744,8 @@
             // When referencing a function, the target type must be a
             // `NativeFunction<T>` so that `T` matches the type from the
             // annotation.
-            if (!targetType.isNativeFunction) {
-              _errorReporter.atNode(
-                node,
-                FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
-                arguments: [targetType, _nativeAddressOf],
-              );
-            } else {
-              var targetFunctionType =
-                  (targetType as InterfaceType).typeArguments[0];
+            if (targetType case InterfaceType(isNativeFunction: true)) {
+              var targetFunctionType = targetType.typeArguments[0];
               if (!typeSystem.isEqualTo(nativeType, targetFunctionType)) {
                 _errorReporter.atNode(
                   node,
@@ -1723,30 +1753,62 @@
                   arguments: [nativeType, targetFunctionType, _nativeAddressOf],
                 );
               }
-            }
-          } else {
-            // A native field is being referenced, this doesn't require a
-            // NativeFunction wrapper. However, we can't read the native type
-            // from the annotation directly because it might be inferred if none
-            // was given.
-            if (nativeType is DynamicType) {
-              var staticType = argument.staticType;
-              if (staticType != null) {
-                var canonical = _canonicalFfiTypeForDartType(staticType);
-
-                if (canonical != null) {
-                  nativeType = canonical;
-                }
-              }
-            }
-
-            if (!typeSystem.isEqualTo(nativeType, targetType)) {
+            } else {
               _errorReporter.atNode(
                 node,
-                FfiCode.MUST_BE_A_SUBTYPE,
-                arguments: [nativeType, targetType, _nativeAddressOf],
+                FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+                arguments: [targetType, _nativeAddressOf],
               );
             }
+          } else {
+            if (argument.staticType case var staticType?
+                when nativeType is DynamicType) {
+              // No type argument was given on the @Native annotation, so we try
+              // to infer the native type from the Dart signature.
+              if (staticType is FunctionType) {
+                if (staticType.returnType is VoidType) {
+                  // The Dart signature has a `void` return type, so we create a
+                  // new `FunctionType` with FFI's `Void` as the return type.
+                  staticType = FunctionTypeImpl.v2(
+                    typeParameters: staticType.typeParameters,
+                    formalParameters: staticType.formalParameters,
+                    returnType: ffiVoidType ??= annotationType.element3.library2
+                        .getClass2('Void')!
+                        .thisType,
+                    nullabilitySuffix: staticType.nullabilitySuffix,
+                  );
+                }
+
+                if (targetType case InterfaceType(isNativeFunction: true)) {
+                  var targetFunctionType = targetType.typeArguments[0];
+                  if (!typeSystem.isEqualTo(staticType, targetFunctionType)) {
+                    _errorReporter.atNode(
+                      node,
+                      FfiCode.MUST_BE_A_SUBTYPE,
+                      arguments: [
+                        staticType,
+                        targetFunctionType,
+                        _nativeAddressOf
+                      ],
+                    );
+                  }
+                } else {
+                  _errorReporter.atNode(
+                    node,
+                    FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
+                    arguments: [targetType, _nativeAddressOf],
+                  );
+                }
+              } else {
+                if (!typeSystem.isEqualTo(staticType, targetType)) {
+                  _errorReporter.atNode(
+                    node,
+                    FfiCode.MUST_BE_A_SUBTYPE,
+                    arguments: [staticType, targetType, _nativeAddressOf],
+                  );
+                }
+              }
+            }
           }
 
           validTarget = true;
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 7233f80..66e6a80 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -19212,6 +19212,49 @@
         external int f;
       }
       ```
+  NATIVE_FUNCTION_MISSING_TYPE:
+    problemMessage: The native type of this function couldn't be inferred so it must be specified in the annotation.
+    correctionMessage: Try adding a type parameter extending `NativeType` to the `@Native` annotation.
+    hasPublishedDocs: false
+    comment: No parameters
+    documentation: |-
+      #### Description
+
+      The analyzer produces this diagnostic when a `@Native`-annotated function
+      requires a type hint on the annotation to infer the native function type.
+
+      Dart types like `int` and `double` have multiple possible native
+      representations. Since the native type needs to be known at compile time
+      to generate correct bindings and call instructions for the function, an
+      explicit type must be given.
+
+      For more information about FFI, see [C interop using dart:ffi][ffi].
+
+      #### Example
+
+      The following code produces this diagnostic because the function `f()` has
+      the return type `int`, but doesn't have an explicit type parameter on the
+      `Native` annotation:
+
+      ```dart
+      import 'dart:ffi';
+
+      @Native()
+      external int [!f!]();
+      ```
+
+      #### Common fixes
+
+      Add the corresponding type to the annotation. For instance, if `f()` was
+      declared to return an `int32_t` in C, the Dart function should be declared
+      as:
+
+      ```dart
+      import 'dart:ffi';
+
+      @Native<Int32 Function()>()
+      external int f();
+      ```
   COMPOUND_IMPLEMENTS_FINALIZABLE:
     problemMessage: "The class '{0}' can't implement Finalizable."
     correctionMessage: "Try removing the implements clause from '{0}'."
diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
index 2cc05aa..ff0b1f1 100644
--- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart
@@ -78,6 +78,21 @@
 import 'dart:ffi';
 
 @Native()
+external void foo();
+
+void main() {
+  print(Native.addressOf(foo));
+}
+''', [
+      error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 74, 21),
+    ]);
+  }
+
+  test_invalid_MissingType3() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
 external Pointer<IntPtr> global;
 
 void main() => print(Native.addressOf(global));
@@ -116,6 +131,19 @@
     ]);
   }
 
+  test_invalid_NotAPreciseType2() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external void foo();
+
+void main() => print(Native.addressOf<NativeFunction>(foo));
+''', [
+      error(FfiCode.MUST_BE_A_SUBTYPE, 73, 37),
+    ]);
+  }
+
   test_invalid_String() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
@@ -134,10 +162,14 @@
 external void foo();
 
 @Native()
+external void foo2();
+
+@Native()
 external Pointer<IntPtr> global;
 
 void main() {
   print(Native.addressOf<NativeFunction<Void Function()>>(foo));
+  print(Native.addressOf<NativeFunction<Void Function()>>(foo2));
   print(Native.addressOf<Pointer<IntPtr>>(global));
 }
 ''');
@@ -235,7 +267,7 @@
 @Native()
 external int foo();
 ''', [
-      error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 43, 3),
+      error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 43, 3),
     ]);
   }
 
@@ -570,7 +602,7 @@
 @Native()
 external int foo();
 ''', [
-      error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 43, 3),
+      error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 43, 3),
     ]);
   }
 
@@ -583,7 +615,7 @@
 @a
 external int foo();
 ''', [
-      error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 57, 3),
+      error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 57, 3),
     ]);
   }
 
@@ -614,6 +646,234 @@
     ]);
   }
 
+  test_InferPointerReturnNoParameters() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Pointer foo();
+''');
+  }
+
+  test_InferPointerReturnPointerParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Pointer foo(Pointer x);
+''');
+  }
+
+  test_InferPointerReturnStructParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Pointer foo(MyStruct x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+''');
+  }
+
+  test_InferPointerReturnUnionParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external Pointer foo(MyUnion x);
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferStructReturnNoParameters() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyStruct foo();
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+''');
+  }
+
+  test_InferStructReturnPointerParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyStruct foo(Pointer x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+''');
+  }
+
+  test_InferStructReturnStructParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyStruct foo(MyStruct x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+''');
+  }
+
+  test_InferStructReturnUnionParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyStruct foo(MyUnion x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferUnionReturnNoParameters() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyUnion foo();
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferUnionReturnPointerParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyUnion foo(Pointer x);
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferUnionReturnStructParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyUnion foo(MyStruct x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferUnionReturnUnionParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external MyUnion foo(MyUnion x);
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
+  test_InferVoidReturnNoParameters() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external void foo();
+''');
+  }
+
+  test_InferVoidReturnPointerParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external void foo(Pointer x);
+''');
+  }
+
+  test_InferVoidReturnStructParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external void foo(MyStruct x);
+
+final class MyStruct extends Struct {
+  @Int8()
+  external int value;
+}
+''');
+  }
+
+  test_InferVoidReturnUnionParameter() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+@Native()
+external void foo(MyUnion x);
+
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+''');
+  }
+
   test_NativeCanUseHandles() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index 0ce095d..8d206a8 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -14424,6 +14424,49 @@
 }
 ```
 
+### native_function_missing_type
+
+_The native type of this function couldn't be inferred so it must be specified
+in the annotation._
+
+#### Description
+
+The analyzer produces this diagnostic when a `@Native`-annotated function
+requires a type hint on the annotation to infer the native function type.
+
+Dart types like `int` and `double` have multiple possible native
+representations. Since the native type needs to be known at compile time
+to generate correct bindings and call instructions for the function, an
+explicit type must be given.
+
+For more information about FFI, see [C interop using dart:ffi][ffi].
+
+#### Example
+
+The following code produces this diagnostic because the function `f()` has
+the return type `int`, but doesn't have an explicit type parameter on the
+`Native` annotation:
+
+```dart
+import 'dart:ffi';
+
+@Native()
+external int [!f!]();
+```
+
+#### Common fixes
+
+Add the corresponding type to the annotation. For instance, if `f()` was
+declared to return an `int32_t` in C, the Dart function should be declared
+as:
+
+```dart
+import 'dart:ffi';
+
+@Native<Int32 Function()>()
+external int f();
+```
+
 ### negative_variable_dimension
 
 _The variable dimension of a variable-length array must be non-negative._
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index bf10cfd..71ddca5 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5276,6 +5276,12 @@
   analyzerCode: NATIVE_FIELD_INVALID_TYPE
   external: test/ffi_test.dart
 
+FfiNativeFunctionMissingType:
+  # Used by dart:ffi
+  problemMessage: "The native type of this function couldn't be inferred so it must be specified in the annotation."
+  analyzerCode: NATIVE_FUNCTION_MISSING_TYPE
+  external: test/ffi_test.dart
+
 FfiAddressOfMustBeNative:
   # Used by dart:ffi
   problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native."
diff --git a/pkg/vm/lib/modular/transformations/ffi/common.dart b/pkg/vm/lib/modular/transformations/ffi/common.dart
index 8165e05..1c8c1ed 100644
--- a/pkg/vm/lib/modular/transformations/ffi/common.dart
+++ b/pkg/vm/lib/modular/transformations/ffi/common.dart
@@ -843,14 +843,42 @@
   ///
   /// For types where this returns a non-null value, this is the inverse of
   /// [convertNativeTypeToDartType].
-  DartType? convertDartTypeToNativeType(DartType dartType) {
-    if (isPointerType(dartType) ||
-        isStructOrUnionSubtype(dartType) ||
-        isArrayType(dartType)) {
+  DartType? convertDartTypeToNativeType(
+    DartType dartType, {
+    bool allowVoid = false,
+  }) {
+    if (allowVoid && dartType is VoidType) return voidType;
+
+    if (isArrayType(dartType) ||
+        isPointerType(dartType) ||
+        isStructOrUnionSubtype(dartType)) {
       return dartType;
-    } else {
-      return null;
     }
+
+    if (dartType is FunctionType) {
+      if (dartType.namedParameters.isNotEmpty) return null;
+      if (dartType.positionalParameters.length !=
+          dartType.requiredParameterCount) {
+        return null;
+      }
+      if (dartType.typeParameters.isNotEmpty) return null;
+
+      final returnType =
+          convertDartTypeToNativeType(dartType.returnType, allowVoid: true);
+      if (returnType == null) return null;
+
+      final argumentTypes = <DartType>[];
+      for (final paramDartType
+          in flattenVarargs(dartType).positionalParameters) {
+        argumentTypes.add(
+          convertDartTypeToNativeType(paramDartType) ?? dummyDartType,
+        );
+      }
+      if (argumentTypes.contains(dummyDartType)) return null;
+      return FunctionType(argumentTypes, returnType, Nullability.nonNullable);
+    }
+
+    return null;
   }
 
   /// Removes the VarArgs from a DartType list.
@@ -1468,7 +1496,7 @@
     bool allowInlineArray = false,
     bool allowVoid = false,
   }) {
-    if (!_nativeTypeValid(nativeType,
+    if (!isNativeTypeValid(nativeType,
         allowStructAndUnion: allowStructAndUnion,
         allowHandle: allowHandle,
         allowInlineArray: allowInlineArray,
@@ -1484,7 +1512,7 @@
 
   /// The Dart type system does not enforce that NativeFunction return and
   /// parameter types are only NativeTypes, so we need to check this.
-  bool _nativeTypeValid(
+  bool isNativeTypeValid(
     DartType nativeType, {
     bool allowStructAndUnion = false,
     bool allowHandle = false,
diff --git a/pkg/vm/lib/modular/transformations/ffi/native.dart b/pkg/vm/lib/modular/transformations/ffi/native.dart
index 5dee1ed..e228a12 100644
--- a/pkg/vm/lib/modular/transformations/ffi/native.dart
+++ b/pkg/vm/lib/modular/transformations/ffi/native.dart
@@ -11,6 +11,7 @@
         messageFfiNativeFieldMissingType,
         messageFfiNativeFieldMustBeStatic,
         messageFfiNativeFieldType,
+        messageFfiNativeFunctionMissingType,
         messageFfiNativeMustBeExternal,
         messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
         templateCantHaveNamedParameters,
@@ -793,6 +794,43 @@
     return (ffiType, NativeTypeCfe.withoutLayout(this, ffiType));
   }
 
+  DartType _validateOrInferNativeFunctionType(
+    Procedure node,
+    DartType nativeType,
+    FunctionType dartType,
+  ) {
+    if (nativeType is DynamicType) {
+      // No type argument was given on the @Native annotation, so we try to
+      // infer the native type from the Dart signature.
+      final inferred = convertDartTypeToNativeType(dartType, allowVoid: true);
+      if (inferred != null) {
+        nativeType = inferred;
+      }
+
+      final nativeFunctionType = InterfaceType(
+        nativeFunctionClass,
+        Nullability.nonNullable,
+        [nativeType],
+      );
+
+      if (!isNativeTypeValid(
+        nativeFunctionType,
+        allowHandle: true,
+        allowStructAndUnion: true,
+      )) {
+        diagnosticReporter.report(
+          messageFfiNativeFunctionMissingType,
+          node.fileOffset,
+          1,
+          node.location?.file,
+        );
+        throw FfiStaticTypeError();
+      }
+    }
+
+    return nativeType;
+  }
+
   @override
   TreeNode visitField(Field node) {
     final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node);
@@ -829,6 +867,18 @@
     final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
     var nativeType = ffiConstant.typeArguments[0];
 
+    if (node.kind == ProcedureKind.Method) {
+      final dartType =
+          node.function.computeFunctionType(Nullability.nonNullable);
+      try {
+        nativeType =
+            _validateOrInferNativeFunctionType(node, nativeType, dartType);
+      } on FfiStaticTypeError {
+        // We've already reported an error.
+        return node;
+      }
+    }
+
     final nativeName = _resolveNativeSymbolName(node, ffiConstant);
     final overriddenAssetName = _assetNameFromAnnotation(ffiConstant);
     final isLeaf = _isLeaf(ffiConstant);
@@ -843,14 +893,13 @@
         // We've already reported an error.
         return node;
       }
-      final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
 
       if (!node.isStatic) {
-        return _transformInstanceMethod(node, ffiFunctionType, nativeName,
+        return _transformInstanceMethod(node, nativeType, nativeName,
             overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
       }
 
-      return _transformStaticFunction(node, ffiFunctionType, nativeName,
+      return _transformStaticFunction(node, nativeType, nativeName,
           overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
     } else if (node.kind == ProcedureKind.Getter ||
         node.kind == ProcedureKind.Setter) {
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart b/pkg/vm/testcases/transformations/ffi/ffinative.dart
index 313780c..2b04048 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart
@@ -11,6 +11,12 @@
 import 'dart:ffi';
 import 'dart:nativewrappers';
 
+@Native()
+external void returnVoid();
+
+@Native()
+external Pointer returnPointer(Pointer x);
+
 @Native<IntPtr Function(IntPtr)>(symbol: 'ReturnIntPtr')
 external int returnIntPtr(int x);
 
@@ -69,5 +75,7 @@
   final b = NativeClassy().myField;
   NativeClassy().myField = !b;
 
+  Native.addressOf<NativeFunction<Void Function()>>(returnVoid);
+  Native.addressOf<NativeFunction<Pointer Function(Pointer)>>(returnPointer);
   Native.addressOf<NativeFunction<IntPtr Function(IntPtr)>>(returnIntPtr);
 }
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
index 8eb300f..b46e9ca 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
@@ -199,6 +199,8 @@
   [@vm.direct-call.metadata=#lib::NativeClassy.blah] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::blah}(){() → core::bool};
   final core::bool b = [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=dart.core::bool] new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
   [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
+  ffi::Native::_addressOf<ffi::NativeFunction<() → ffi::Void>>(#C48);
+  ffi::Native::_addressOf<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Pointer<ffi::NativeType>>>(#C50);
   ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
 }
 constants  {
@@ -248,4 +250,8 @@
   #C44 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C28}
   #C45 = core::pragma {name:#C1, options:#C44}
   #C46 = core::pragma {name:#C7, options:#C44}
+  #C47 = "returnVoid"
+  #C48 = ffi::Native<() → ffi::Void> {symbol:#C47, assetId:#C3, isLeaf:#C4}
+  #C49 = "returnPointer"
+  #C50 = ffi::Native<(ffi::Pointer<ffi::NativeType>) → ffi::Pointer<ffi::NativeType>> {symbol:#C49, assetId:#C3, isLeaf:#C4}
 }
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
index ab86316..5d77cdc 100644
--- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
@@ -144,14 +144,20 @@
   @#C42
   external static method _myField$Setter$FfiNative(ffi::Pointer<ffi::Void> #t0, core::bool #t1) → void;
 }
+@#C45
+@#C46
+external static method returnVoid() → void;
+@#C49
+@#C50
+external static method returnPointer(ffi::Pointer<ffi::NativeType> x) → ffi::Pointer<ffi::NativeType>;
 @#C6
 @#C8
 external static method returnIntPtr(core::int x) → core::int;
-@#C44
-@#C45
+@#C52
+@#C53
 external static method returnIntPtrLeaf(core::int x) → core::int;
-@#C48
-@#C49
+@#C56
+@#C57
 external static method returnNativeIntPtrLeaf(core::int x) → core::int;
 static method main() → void {
   self::returnIntPtr(13);
@@ -166,6 +172,8 @@
   new self::NativeClassy::•().{self::NativeClassy::blah}(){() → core::bool};
   final core::bool b = new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool};
   new self::NativeClassy::•().{self::NativeClassy::myField} = !b;
+  ffi::Native::_addressOf<ffi::NativeFunction<() → ffi::Void>>(#C44);
+  ffi::Native::_addressOf<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Pointer<ffi::NativeType>>>(#C48);
   ffi::Native::_addressOf<ffi::NativeFunction<(ffi::IntPtr) → ffi::IntPtr>>(#C5);
 }
 constants  {
@@ -211,11 +219,19 @@
   #C40 = core::pragma {name:#C7, options:#C27}
   #C41 = core::pragma {name:#C7, options:#C30}
   #C42 = core::pragma {name:#C7, options:#C32}
-  #C43 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C29}
-  #C44 = core::pragma {name:#C1, options:#C43}
-  #C45 = core::pragma {name:#C7, options:#C43}
-  #C46 = "returnNativeIntPtrLeaf"
-  #C47 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C46, assetId:#C3, isLeaf:#C29}
-  #C48 = core::pragma {name:#C1, options:#C47}
-  #C49 = core::pragma {name:#C7, options:#C47}
+  #C43 = "returnVoid"
+  #C44 = ffi::Native<() → ffi::Void> {symbol:#C43, assetId:#C3, isLeaf:#C4}
+  #C45 = core::pragma {name:#C1, options:#C44}
+  #C46 = core::pragma {name:#C7, options:#C44}
+  #C47 = "returnPointer"
+  #C48 = ffi::Native<(ffi::Pointer<ffi::NativeType>) → ffi::Pointer<ffi::NativeType>> {symbol:#C47, assetId:#C3, isLeaf:#C4}
+  #C49 = core::pragma {name:#C1, options:#C48}
+  #C50 = core::pragma {name:#C7, options:#C48}
+  #C51 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C29}
+  #C52 = core::pragma {name:#C1, options:#C51}
+  #C53 = core::pragma {name:#C7, options:#C51}
+  #C54 = "returnNativeIntPtrLeaf"
+  #C55 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C54, assetId:#C3, isLeaf:#C29}
+  #C56 = core::pragma {name:#C1, options:#C55}
+  #C57 = core::pragma {name:#C7, options:#C55}
 }
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 10bfda2..c0f8d7b 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -2374,20 +2374,30 @@
 /// annotated function or variable in Dart. This can be overridden with the
 /// [symbol] parameter on the annotation.
 ///
-/// If this annotation is used on a function, then the type argument [T] to the
-/// [Native] annotation must be a function type representing the native
-/// function's parameter and return types. The parameter and return types must
-/// be subtypes of [NativeType].
+/// When used on a function, [T] must be a function type that represents the
+/// native function's parameter and return types. The parameter and return types
+/// must be subtypes of [NativeType].
 ///
-/// If this annotation is used on an external variable, then the type argument
-/// [T] must be a compatible native type. For example, an [int] field can be
-/// annotated with [Int32].
-/// If the type argument to `@Native` is omitted, it defaults to the Dart type
-/// of the annotated declaration, which *must* then be a native type too.
-/// This will never work for function declarations, but can apply to variables
-/// whose type is some of the types of this library, such as [Pointer].
-/// For native global variables that cannot be re-assigned, a final variable in
-/// Dart or a getter can be used to prevent assignments to the native field.
+/// When used on a variable, [T] must be a compatible native type. For example,
+/// an [int] field can be annotated with [Int32].
+///
+/// If the type argument [T] is omitted in the `@Native` annotation, it is
+/// inferred from the static type of the declaration, which must meet the
+/// following constraints:
+///
+/// For function or method declarations:
+/// - The return type must be one of the following:
+///   - [Pointer]
+///   - `void`
+///   - Subtype of compound types, such as [Struct] or [Union]
+/// - The parameter types must be subtypes of compound types or [Pointer]
+///
+/// For variable declarations, the type can be any of the following:
+///   - [Pointer]
+///   - Subtype of compound types, such as [Struct] or [Union]
+///
+/// For native global variables that cannot be reassigned, a `final` variable in
+/// Dart or a getter can be used to prevent modifications to the native field.
 ///
 /// Example:
 ///
@@ -2395,6 +2405,9 @@
 /// @Native<Int64 Function(Int64, Int64)>()
 /// external int sum(int a, int b);
 ///
+/// @Native()
+/// external void free(Pointer p);
+///
 /// @Native<Int64>()
 /// external int aGlobalInt;
 ///
diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
index 5c3e7c6..d3669f2 100644
--- a/tests/ffi/native_assets/asset_absolute_test.dart
+++ b/tests/ffi/native_assets/asset_absolute_test.dart
@@ -113,7 +113,7 @@
 @Native()
 external Coord globalStruct;
 
-@Native<Coord Function()>()
+@Native()
 external Coord GetGlobalStruct();
 
 @Native()
diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
index 2a99623..2ffb3f2 100644
--- a/tests/ffi/native_assets/asset_library_annotation_test.dart
+++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
@@ -111,7 +111,7 @@
 @Native()
 external Coord globalStruct;
 
-@Native<Coord Function()>()
+@Native()
 external Coord GetGlobalStruct();
 
 @Native()
diff --git a/tests/ffi/native_assets/asset_process_test.dart b/tests/ffi/native_assets/asset_process_test.dart
index 9242eef..f6c6850 100644
--- a/tests/ffi/native_assets/asset_process_test.dart
+++ b/tests/ffi/native_assets/asset_process_test.dart
@@ -79,25 +79,25 @@
 @Native<Pointer Function(IntPtr)>(symbol: 'malloc')
 external Pointer posixMalloc(int size);
 
-@Native<Void Function(Pointer)>(symbol: 'free')
+@Native(symbol: 'free')
 external void posixFree(Pointer pointer);
 
 @Native<Pointer Function(Size)>(symbol: 'CoTaskMemAlloc')
 external Pointer winCoTaskMemAlloc(int cb);
 
-@Native<Void Function(Pointer)>(symbol: 'CoTaskMemFree')
+@Native(symbol: 'CoTaskMemFree')
 external void winCoTaskMemFree(Pointer pv);
 
 @Native<Pointer Function(IntPtr)>()
 external Pointer malloc(int size);
 
-@Native<Void Function(Pointer)>(assetId: asset2Name)
+@Native(assetId: asset2Name)
 external void free(Pointer pointer);
 
 @Native<Pointer Function(Size)>()
 external Pointer CoTaskMemAlloc(int cb);
 
-@Native<Void Function(Pointer)>(assetId: asset2Name)
+@Native(assetId: asset2Name)
 external void CoTaskMemFree(Pointer pv);
 
 void testProcessOrSystem() {
diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
index d7586e5..7042791 100644
--- a/tests/ffi/native_assets/asset_relative_test.dart
+++ b/tests/ffi/native_assets/asset_relative_test.dart
@@ -193,7 +193,7 @@
 @Native()
 external Coord globalStruct;
 
-@Native<Coord Function()>()
+@Native()
 external Coord GetGlobalStruct();
 
 @Native()
diff --git a/tests/ffi/native_assets/asset_system_test.dart b/tests/ffi/native_assets/asset_system_test.dart
index dc9ec63..e421e62 100644
--- a/tests/ffi/native_assets/asset_system_test.dart
+++ b/tests/ffi/native_assets/asset_system_test.dart
@@ -81,13 +81,13 @@
 @Native<Pointer Function(IntPtr)>()
 external Pointer malloc(int size);
 
-@Native<Void Function(Pointer)>(assetId: asset2Name)
+@Native(assetId: asset2Name)
 external void free(Pointer pointer);
 
 @Native<Pointer Function(Size)>()
 external Pointer CoTaskMemAlloc(int cb);
 
-@Native<Void Function(Pointer)>(assetId: asset2Name)
+@Native(assetId: asset2Name)
 external void CoTaskMemFree(Pointer pv);
 
 void testProcessOrSystem() {
diff --git a/tests/ffi/native_assets/process_test.dart b/tests/ffi/native_assets/process_test.dart
index f734146..aadcd0e 100644
--- a/tests/ffi/native_assets/process_test.dart
+++ b/tests/ffi/native_assets/process_test.dart
@@ -28,13 +28,13 @@
 @Native<Pointer Function(IntPtr, IntPtr)>(symbol: 'calloc')
 external Pointer posixCalloc(int num, int size);
 
-@Native<Void Function(Pointer)>(symbol: 'free')
+@Native(symbol: 'free')
 external void posixFree(Pointer pointer);
 
 @Native<Pointer Function(Size)>(symbol: 'CoTaskMemAlloc')
 external Pointer winCoTaskMemAlloc(int cb);
 
-@Native<Void Function(Pointer)>(symbol: 'CoTaskMemFree')
+@Native(symbol: 'CoTaskMemFree')
 external void winCoTaskMemFree(Pointer pv);
 
 class _MallocAllocator implements Allocator {
@@ -90,5 +90,5 @@
   }
 }
 
-@Native<Void Function()>(symbol: 'symbol_is_not_defined_29903211')
+@Native(symbol: 'symbol_is_not_defined_29903211')
 external void symbolIsNotDefined();
diff --git a/tests/ffi/static_checks/regress_51913_test.dart b/tests/ffi/static_checks/regress_51913_test.dart
deleted file mode 100644
index b2dd94f..0000000
--- a/tests/ffi/static_checks/regress_51913_test.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2021, 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.
-
-// Formatting can break multitests, so don't format them.
-// dart format off
-
-import 'dart:ffi';
-
-// Should have been `@Native<Void Function()>` or something.
-@Native() //# 1: compile-time error
-external void foo(); //# 1: compile-time error
-
-void main() {
-  print('something');
-}
diff --git a/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart b/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart
index 438bf0b..9f7517a 100644
--- a/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart
+++ b/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart
@@ -27,6 +27,89 @@
   external Pointer<MyStruct> next;
 }
 
+final class MyUnion extends Union {
+  @Int8()
+  external int a;
+  @Int8()
+  external int b;
+}
+
+@Native()
+external void validInference();
+
+@Native()
+external void validInference2(Pointer x);
+
+@Native()
+external void validInference3(MyStruct x);
+
+@Native()
+external void validInference4(MyUnion x);
+
+@Native()
+external MyStruct validInference5();
+
+@Native()
+external MyStruct validInference6(Pointer x);
+
+@Native()
+external MyStruct validInference7(MyStruct x);
+
+@Native()
+external MyStruct validInference8(MyUnion x);
+
+@Native()
+external MyUnion validInference9();
+
+@Native()
+external MyUnion validInference10(Pointer x);
+
+@Native()
+external MyUnion validInference11(MyStruct x);
+
+@Native()
+external MyUnion validInference12(MyUnion x);
+
+@Native()
+external Pointer validInference13();
+
+@Native()
+external Pointer validInference14(Pointer x);
+
+@Native()
+external Pointer validInference15(MyStruct x);
+
+@Native()
+external Pointer validInference16(MyUnion x);
+
+@Native()
+external void invalidNoInference(int x);
+//            ^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE
+//            ^
+// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation.
+
+@Native()
+external MyStruct invalidNoInference2(int x);
+//                ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE
+//                ^
+// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation.
+
+@Native()
+external MyUnion invalidNoInference3(int x);
+//               ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE
+//               ^
+// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation.
+
+@Native()
+external Pointer invalidNoInference4(int x);
+//               ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE
+//               ^
+// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation.
+
 typedef ComplexNativeFunction = MyStruct Function(Long, Double, MyStruct);
 const native = Native<ComplexNativeFunction>();
 
@@ -68,7 +151,7 @@
 // [cfe] Expected type 'Pointer<MyStruct>' to be 'MyStruct', which is the Dart type corresponding to 'MyStruct'.
 
 @Native()
-external int invalidNoInferrence;
+external int invalidNoInference5;
 //           ^^^^^^^^^^^^^^^^^^^
 // [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_MISSING_TYPE
 // [cfe] The native type of this field could not be inferred and must be specified in the annotation.
@@ -171,18 +254,35 @@
   //     ^
   // [cfe] Expected type 'NativeType' to be a valid and instantiated subtype of 'NativeType'.
 
+  Native.addressOf(validInference);
+//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
+  //     ^
+  // [cfe] Expected type 'NativeType' to be a valid and instantiated subtype of 'NativeType'.
+
   Native.addressOf<NativeFunction>(_valid);
 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 // [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
   //     ^
   // [cfe] Expected type 'NativeFunction<Function>' to be a valid and instantiated subtype of 'NativeType'.
 
+  Native.addressOf<NativeFunction>(validInference);
+//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+  //     ^
+  // [cfe] Expected type 'NativeFunction<Function>' to be a valid and instantiated subtype of 'NativeType'.
+
   Native.addressOf<NativeFunction<Void Function(Int)>>(_valid);
 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 // [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
   //     ^
   // [cfe] Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction<Void Function(Int)>'.
 
+  Native.addressOf<NativeFunction<Void Function()>>(validInference);
+  Native.addressOf<NativeFunction<Void Function(Pointer)>>(validInference2);
+  Native.addressOf<NativeFunction<MyStruct Function()>>(validInference5);
+  Native.addressOf<NativeFunction<MyUnion Function()>>(validInference9);
+  Native.addressOf<NativeFunction<Pointer Function()>>(validInference13);
   Native.addressOf<NativeFunction<ComplexNativeFunction>>(validNative);
 
   Native.addressOf(myStruct0);
diff --git a/tools/find_builders.dart b/tools/find_builders.dart
index 7ddb178..5d9b53e 100755
--- a/tools/find_builders.dart
+++ b/tools/find_builders.dart
@@ -8,7 +8,7 @@
 // Usage:
 //
 // ```
-// $ tools/find_builders.dart ffi/regress_51504_test ffi/regress_51913_test
+// $ tools/find_builders.dart ffi/regress_51504_test ffi/regress_52298_test
 // Cq-Include-Trybots: dart/try:vm-kernel-linux-debug-x64,...
 // ```
 
@@ -27,10 +27,11 @@
     for (final testName in testNames) ...await _testGetConfigurations(testName),
   });
   final configurationBuilders = await _configurationBuilders();
-  final builders = _filterBuilders(
-    {for (final config in configurations) configurationBuilders[config]!},
-  ).toList()
-    ..sort();
+  final builders =
+      _filterBuilders({
+          for (final config in configurations) configurationBuilders[config]!,
+        }).toList()
+        ..sort();
 
   final gerritTryList = builders.map((b) => '$b-try').join(',');
   print('Cq-Include-Trybots: dart/try:$gerritTryList');
@@ -47,7 +48,7 @@
   final object = jsonDecode(response) as Map<String, dynamic>;
   return [
     for (final result in ((object['results'] as List)).cast<Map>())
-      result['configuration']
+      result['configuration'],
   ];
 }
 
@@ -105,7 +106,8 @@
     final response = await _get(requestUrl);
     final object = jsonDecode(response) as Map<String, dynamic>;
     yield* Stream.fromIterable(
-        (object['documents'] as List).cast<Map<String, dynamic>>());
+      (object['documents'] as List).cast<Map<String, dynamic>>(),
+    );
 
     nextPageToken = object['nextPageToken'];
   } while (nextPageToken != null);
@@ -114,12 +116,11 @@
 Future<Map<String, String>> _configurationBuilders() async {
   return {
     await for (final document in _configurationDocuments())
-      if (document
-          case {
-            'name': String fullName,
-            'fields': {'builder': {'stringValue': String builder}}
-          })
-        fullName.split('/').last: builder
+      if (document case {
+        'name': String fullName,
+        'fields': {'builder': {'stringValue': String builder}},
+      })
+        fullName.split('/').last: builder,
   };
 }