[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>
This commit is contained in:
Halil Durmus
2024-12-20 02:05:40 -08:00
committed by Commit Queue
parent 31f97eaec1
commit 1a8afa6bbf
24 changed files with 763 additions and 119 deletions
@@ -6647,6 +6647,18 @@ const MessageCode messageFfiNativeFieldType = const MessageCode(
r"""Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.""",
);
// 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;
@@ -1792,6 +1792,8 @@ FfiCode.NATIVE_FIELD_MISSING_TYPE:
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:
@@ -387,6 +387,16 @@ class FfiCode extends ErrorCode {
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',
@@ -632,6 +632,7 @@ const List<ErrorCode> errorCodeValues = [
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,
+101 -39
View File
@@ -110,6 +110,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
/// 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 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
);
}
} 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 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
);
}
}
if (ffiSignature is FunctionType &&
declarationElement is ExecutableElement2) {}
}
}
@@ -683,11 +711,20 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
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 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
// 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 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
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;
+43
View File
@@ -19212,6 +19212,49 @@ FfiCode:
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}'."
@@ -77,6 +77,21 @@ void main() {
await assertErrorsInCode(r'''
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;
@@ -116,6 +131,19 @@ void main() => print(Native.addressOf<NativeFunction>(foo));
]);
}
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';
@@ -133,11 +161,15 @@ import 'dart:ffi';
@Native<Void Function()>()
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 @@ import 'dart:ffi';
@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 @@ import 'dart:ffi';
@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 @@ const a = Native();
@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 @@ external int foo();
]);
}
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';
@@ -14424,6 +14424,49 @@ final class C extends Struct {
}
```
### 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._
+6
View File
@@ -5276,6 +5276,12 @@ FfiNativeFieldType:
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."
@@ -843,14 +843,42 @@ class FfiTransformer extends Transformer {
///
/// 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 @@ class FfiTransformer extends Transformer {
bool allowInlineArray = false,
bool allowVoid = false,
}) {
if (!_nativeTypeValid(nativeType,
if (!isNativeTypeValid(nativeType,
allowStructAndUnion: allowStructAndUnion,
allowHandle: allowHandle,
allowInlineArray: allowInlineArray,
@@ -1484,7 +1512,7 @@ class FfiTransformer extends Transformer {
/// 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,
@@ -11,6 +11,7 @@ import 'package:front_end/src/codes/cfe_codes.dart'
messageFfiNativeFieldMissingType,
messageFfiNativeFieldMustBeStatic,
messageFfiNativeFieldType,
messageFfiNativeFunctionMissingType,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
templateCantHaveNamedParameters,
@@ -793,6 +794,43 @@ class FfiNativeTransformer extends FfiTransformer {
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 @@ class FfiNativeTransformer extends FfiTransformer {
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 @@ class FfiNativeTransformer extends FfiTransformer {
// 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) {
@@ -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 @@ void main() {
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);
}
@@ -199,6 +199,8 @@ static method main() → void {
[@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 @@ constants {
#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}
}
@@ -144,14 +144,20 @@ class NativeClassy extends nat::NativeFieldWrapperClass1 {
@#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 @@ static method main() → void {
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 @@ constants {
#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}
}
+26 -13
View File
@@ -2374,20 +2374,30 @@ abstract final class NativeApi {
/// 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 @@ abstract final class NativeApi {
/// @Native<Int64 Function(Int64, Int64)>()
/// external int sum(int a, int b);
///
/// @Native()
/// external void free(Pointer p);
///
/// @Native<Int64>()
/// external int aGlobalInt;
///
@@ -113,7 +113,7 @@ final class Coord extends Struct {
@Native()
external Coord globalStruct;
@Native<Coord Function()>()
@Native()
external Coord GetGlobalStruct();
@Native()
@@ -111,7 +111,7 @@ final class Coord extends Struct {
@Native()
external Coord globalStruct;
@Native<Coord Function()>()
@Native()
external Coord GetGlobalStruct();
@Native()
@@ -79,25 +79,25 @@ Future<void> runTests() async {
@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() {
@@ -193,7 +193,7 @@ final class Coord extends Struct {
@Native()
external Coord globalStruct;
@Native<Coord Function()>()
@Native()
external Coord GetGlobalStruct();
@Native()
@@ -81,13 +81,13 @@ Future<void> runTests() async {
@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() {
+3 -3
View File
@@ -28,13 +28,13 @@ external Pointer posixMalloc(int size);
@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 @@ void testFailure() {
}
}
@Native<Void Function()>(symbol: 'symbol_is_not_defined_29903211')
@Native(symbol: 'symbol_is_not_defined_29903211')
external void symbolIsNotDefined();
@@ -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');
}
@@ -27,6 +27,89 @@ final class MyStruct extends Struct {
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 @@ external Pointer<MyStruct> myStructPtrInvalid;
// [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 @@ void addressOf() {
// ^
// [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);
+14 -13
View File
@@ -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 @@ Future<void> main(List<String> args) async {
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 @@ Future<List<String>> _testGetConfigurations(String testName) async {
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 @@ Stream<Map<String, dynamic>> _configurationDocuments() async* {
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 @@ Stream<Map<String, dynamic>> _configurationDocuments() async* {
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,
};
}