Skip to content

Commit 1a8afa6

Browse files
halildurmusCommit Queue
authored andcommitted
[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: #54810 Change-Id: Ied5407fcd2f49d85284cb7817f0c8cad2a73626b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400840 Reviewed-by: Daco Harkes <[email protected]> Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Moritz Sümmermann <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 31f97ea commit 1a8afa6

File tree

24 files changed

+762
-118
lines changed

24 files changed

+762
-118
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6647,6 +6647,18 @@ const MessageCode messageFfiNativeFieldType = const MessageCode(
66476647
r"""Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.""",
66486648
);
66496649

6650+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6651+
const Code<Null> codeFfiNativeFunctionMissingType =
6652+
messageFfiNativeFunctionMissingType;
6653+
6654+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
6655+
const MessageCode messageFfiNativeFunctionMissingType = const MessageCode(
6656+
"FfiNativeFunctionMissingType",
6657+
analyzerCodes: <String>["NATIVE_FUNCTION_MISSING_TYPE"],
6658+
problemMessage:
6659+
r"""The native type of this function couldn't be inferred so it must be specified in the annotation.""",
6660+
);
6661+
66506662
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
66516663
const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
66526664

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,8 @@ FfiCode.NATIVE_FIELD_MISSING_TYPE:
17921792
status: needsEvaluation
17931793
FfiCode.NATIVE_FIELD_NOT_STATIC:
17941794
status: needsEvaluation
1795+
FfiCode.NATIVE_FUNCTION_MISSING_TYPE:
1796+
status: needsEvaluation
17951797
FfiCode.NEGATIVE_VARIABLE_DIMENSION:
17961798
status: noFix
17971799
FfiCode.NON_CONSTANT_TYPE_ARGUMENT:

pkg/analyzer/lib/src/dart/error/ffi_code.g.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,16 @@ class FfiCode extends ErrorCode {
387387
hasPublishedDocs: true,
388388
);
389389

390+
/// No parameters
391+
static const FfiCode NATIVE_FUNCTION_MISSING_TYPE = FfiCode(
392+
'NATIVE_FUNCTION_MISSING_TYPE',
393+
"The native type of this function couldn't be inferred so it must be "
394+
"specified in the annotation.",
395+
correctionMessage:
396+
"Try adding a type parameter extending `NativeType` to the `@Native` "
397+
"annotation.",
398+
);
399+
390400
/// No parameters.
391401
static const FfiCode NEGATIVE_VARIABLE_DIMENSION = FfiCode(
392402
'NEGATIVE_VARIABLE_DIMENSION',

pkg/analyzer/lib/src/error/error_code_values.g.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ const List<ErrorCode> errorCodeValues = [
632632
FfiCode.NATIVE_FIELD_INVALID_TYPE,
633633
FfiCode.NATIVE_FIELD_MISSING_TYPE,
634634
FfiCode.NATIVE_FIELD_NOT_STATIC,
635+
FfiCode.NATIVE_FUNCTION_MISSING_TYPE,
635636
FfiCode.NEGATIVE_VARIABLE_DIMENSION,
636637
FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
637638
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,

pkg/analyzer/lib/src/generated/ffi_verifier.dart

Lines changed: 100 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
110110
/// Subclass of `Struct` or `Union` we are currently visiting, or `null`.
111111
ClassDeclaration? compound;
112112

113+
/// The `Void` type from `dart:ffi`, or `null` if unresolved.
114+
InterfaceType? ffiVoidType;
115+
113116
/// Initialize a newly created verifier.
114117
FfiVerifier(this.typeSystem, this._errorReporter,
115118
{required this.strictCasts});
@@ -493,8 +496,36 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
493496
);
494497
}
495498
} else {
496-
if (declarationElement is MethodElement2 ||
497-
declarationElement is TopLevelFunctionElement) {
499+
if (declarationElement
500+
case TopLevelFunctionElement() || MethodElement2()) {
501+
declarationElement = declarationElement as ExecutableElement2;
502+
var dartSignature = declarationElement.type;
503+
504+
if (declarationElement.isStatic && ffiSignature is DynamicType) {
505+
// No type argument was given on the @Native annotation, so we try
506+
// to infer the native type from the Dart signature.
507+
if (dartSignature.returnType is VoidType) {
508+
// The Dart signature has a `void` return type, so we create a new
509+
// `FunctionType` with FFI's `Void` as the return type.
510+
dartSignature = FunctionTypeImpl.v2(
511+
typeParameters: dartSignature.typeParameters,
512+
formalParameters: dartSignature.formalParameters,
513+
returnType: ffiVoidType ??= annotationType.element3.library2
514+
.getClass2('Void')!
515+
.thisType,
516+
nullabilitySuffix: dartSignature.nullabilitySuffix,
517+
);
518+
}
519+
_checkFfiNativeFunction(
520+
errorNode,
521+
declarationElement,
522+
dartSignature,
523+
annotationValue,
524+
formalParameters,
525+
);
526+
return;
527+
}
528+
498529
// Function annotated with something that isn't a function type.
499530
_errorReporter.atToken(
500531
errorNode,
@@ -512,9 +543,6 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
512543
);
513544
}
514545
}
515-
516-
if (ffiSignature is FunctionType &&
517-
declarationElement is ExecutableElement2) {}
518546
}
519547
}
520548

@@ -683,11 +711,20 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
683711
nullabilitySuffix: ffiSignature.nullabilitySuffix,
684712
);
685713
if (!_isValidFfiNativeFunctionType(nativeType)) {
686-
_errorReporter.atToken(
687-
errorToken,
688-
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
689-
arguments: [nativeType, 'Native'],
690-
);
714+
var nativeTypeIsOmitted = (annotationValue.type! as InterfaceType)
715+
.typeArguments[0] is DynamicType;
716+
if (nativeTypeIsOmitted) {
717+
_errorReporter.atToken(
718+
errorToken,
719+
FfiCode.NATIVE_FUNCTION_MISSING_TYPE,
720+
);
721+
} else {
722+
_errorReporter.atToken(
723+
errorToken,
724+
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
725+
arguments: [nativeType, 'Native'],
726+
);
727+
}
691728
return;
692729
}
693730
if (!_validateCompatibleFunctionTypes(
@@ -1707,45 +1744,70 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
17071744
// When referencing a function, the target type must be a
17081745
// `NativeFunction<T>` so that `T` matches the type from the
17091746
// annotation.
1710-
if (!targetType.isNativeFunction) {
1711-
_errorReporter.atNode(
1712-
node,
1713-
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
1714-
arguments: [targetType, _nativeAddressOf],
1715-
);
1716-
} else {
1717-
var targetFunctionType =
1718-
(targetType as InterfaceType).typeArguments[0];
1747+
if (targetType case InterfaceType(isNativeFunction: true)) {
1748+
var targetFunctionType = targetType.typeArguments[0];
17191749
if (!typeSystem.isEqualTo(nativeType, targetFunctionType)) {
17201750
_errorReporter.atNode(
17211751
node,
17221752
FfiCode.MUST_BE_A_SUBTYPE,
17231753
arguments: [nativeType, targetFunctionType, _nativeAddressOf],
17241754
);
17251755
}
1756+
} else {
1757+
_errorReporter.atNode(
1758+
node,
1759+
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
1760+
arguments: [targetType, _nativeAddressOf],
1761+
);
17261762
}
17271763
} else {
1728-
// A native field is being referenced, this doesn't require a
1729-
// NativeFunction wrapper. However, we can't read the native type
1730-
// from the annotation directly because it might be inferred if none
1731-
// was given.
1732-
if (nativeType is DynamicType) {
1733-
var staticType = argument.staticType;
1734-
if (staticType != null) {
1735-
var canonical = _canonicalFfiTypeForDartType(staticType);
1736-
1737-
if (canonical != null) {
1738-
nativeType = canonical;
1764+
if (argument.staticType case var staticType?
1765+
when nativeType is DynamicType) {
1766+
// No type argument was given on the @Native annotation, so we try
1767+
// to infer the native type from the Dart signature.
1768+
if (staticType is FunctionType) {
1769+
if (staticType.returnType is VoidType) {
1770+
// The Dart signature has a `void` return type, so we create a
1771+
// new `FunctionType` with FFI's `Void` as the return type.
1772+
staticType = FunctionTypeImpl.v2(
1773+
typeParameters: staticType.typeParameters,
1774+
formalParameters: staticType.formalParameters,
1775+
returnType: ffiVoidType ??= annotationType.element3.library2
1776+
.getClass2('Void')!
1777+
.thisType,
1778+
nullabilitySuffix: staticType.nullabilitySuffix,
1779+
);
17391780
}
1740-
}
1741-
}
17421781

1743-
if (!typeSystem.isEqualTo(nativeType, targetType)) {
1744-
_errorReporter.atNode(
1745-
node,
1746-
FfiCode.MUST_BE_A_SUBTYPE,
1747-
arguments: [nativeType, targetType, _nativeAddressOf],
1748-
);
1782+
if (targetType case InterfaceType(isNativeFunction: true)) {
1783+
var targetFunctionType = targetType.typeArguments[0];
1784+
if (!typeSystem.isEqualTo(staticType, targetFunctionType)) {
1785+
_errorReporter.atNode(
1786+
node,
1787+
FfiCode.MUST_BE_A_SUBTYPE,
1788+
arguments: [
1789+
staticType,
1790+
targetFunctionType,
1791+
_nativeAddressOf
1792+
],
1793+
);
1794+
}
1795+
} else {
1796+
_errorReporter.atNode(
1797+
node,
1798+
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
1799+
arguments: [targetType, _nativeAddressOf],
1800+
);
1801+
}
1802+
} else {
1803+
if (!typeSystem.isEqualTo(staticType, targetType)) {
1804+
_errorReporter.atNode(
1805+
node,
1806+
FfiCode.MUST_BE_A_SUBTYPE,
1807+
arguments: [staticType, targetType, _nativeAddressOf],
1808+
);
1809+
}
1810+
}
17491811
}
17501812
}
17511813

pkg/analyzer/messages.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19212,6 +19212,49 @@ FfiCode:
1921219212
external int f;
1921319213
}
1921419214
```
19215+
NATIVE_FUNCTION_MISSING_TYPE:
19216+
problemMessage: The native type of this function couldn't be inferred so it must be specified in the annotation.
19217+
correctionMessage: Try adding a type parameter extending `NativeType` to the `@Native` annotation.
19218+
hasPublishedDocs: false
19219+
comment: No parameters
19220+
documentation: |-
19221+
#### Description
19222+
19223+
The analyzer produces this diagnostic when a `@Native`-annotated function
19224+
requires a type hint on the annotation to infer the native function type.
19225+
19226+
Dart types like `int` and `double` have multiple possible native
19227+
representations. Since the native type needs to be known at compile time
19228+
to generate correct bindings and call instructions for the function, an
19229+
explicit type must be given.
19230+
19231+
For more information about FFI, see [C interop using dart:ffi][ffi].
19232+
19233+
#### Example
19234+
19235+
The following code produces this diagnostic because the function `f()` has
19236+
the return type `int`, but doesn't have an explicit type parameter on the
19237+
`Native` annotation:
19238+
19239+
```dart
19240+
import 'dart:ffi';
19241+
19242+
@Native()
19243+
external int [!f!]();
19244+
```
19245+
19246+
#### Common fixes
19247+
19248+
Add the corresponding type to the annotation. For instance, if `f()` was
19249+
declared to return an `int32_t` in C, the Dart function should be declared
19250+
as:
19251+
19252+
```dart
19253+
import 'dart:ffi';
19254+
19255+
@Native<Int32 Function()>()
19256+
external int f();
19257+
```
1921519258
COMPOUND_IMPLEMENTS_FINALIZABLE:
1921619259
problemMessage: "The class '{0}' can't implement Finalizable."
1921719260
correctionMessage: "Try removing the implements clause from '{0}'."

0 commit comments

Comments
 (0)