From 4270c6177800a7a5c909d8f43b0a8aad52a56827 Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Wed, 20 Dec 2023 11:48:52 -0600 Subject: [PATCH] Update diagnostic messages and linter rules (#5419) --- src/_data/linter_rules.json | 6 +- src/tools/diagnostic-messages.md | 285 ++++++++++++++++++++++++++++++- 2 files changed, 287 insertions(+), 4 deletions(-) diff --git a/src/_data/linter_rules.json b/src/_data/linter_rules.json index fb9501351e..c78e75552f 100644 --- a/src/_data/linter_rules.json +++ b/src/_data/linter_rules.json @@ -25,7 +25,7 @@ }, { "name": "avoid_empty_else", - "description": "Avoid empty else statements.", + "description": "Avoid empty statements in else clauses.", "group": "errors", "state": "stable", "incompatible": [], @@ -35,7 +35,7 @@ "flutter" ], "fixStatus": "hasFix", - "details": "**AVOID** empty else statements.\n\n**BAD:**\n```dart\nif (x > y)\n print(\"1\");\nelse ;\n print(\"2\");\n```\n\n", + "details": "**AVOID** empty statements in the `else` clause of `if` statements.\n\n**BAD:**\n```dart\nif (x > y)\n print('1');\nelse ;\n print('2');\n```\n\nIf you want a statement that follows the empty clause to _conditionally_ run,\nremove the dangling semicolon to include it in the `else` clause.\nOptionally, also enclose the else's statement in a block.\n\n**GOOD:**\n```dart\nif (x > y)\n print('1');\nelse\n print('2');\n```\n\n**GOOD:**\n```dart\nif (x > y) {\n print('1');\n} else {\n print('2');\n}\n```\n\nIf you want a statement that follows the empty clause to _unconditionally_ run,\nremove the `else` clause.\n\n**GOOD:**\n```dart\nif (x > y) print('1');\n\nprint('2');\n```\n", "sinceDartSdk": "2.0.0" }, { @@ -482,7 +482,7 @@ "flutter" ], "fixStatus": "noFix", - "details": "**DON'T** use BuildContext across asynchronous gaps.\n\nStoring `BuildContext` for later usage can easily lead to difficult to diagnose\ncrashes. Asynchronous gaps are implicitly storing `BuildContext` and are some of\nthe easiest to overlook when writing code.\n\nWhen a `BuildContext` is used, its `mounted` property must be checked after an\nasynchronous gap.\n\n**BAD:**\n```dart\nvoid onButtonTapped(BuildContext context) async {\n await Future.delayed(const Duration(seconds: 1));\n Navigator.of(context).pop();\n}\n```\n\n**GOOD:**\n```dart\nvoid onButtonTapped(BuildContext context) {\n Navigator.of(context).pop();\n}\n```\n\n**GOOD:**\n```dart\nvoid onButtonTapped() async {\n await Future.delayed(const Duration(seconds: 1));\n\n if (!context.mounted) return;\n Navigator.of(context).pop();\n}\n```\n", + "details": "**DON'T** use BuildContext across asynchronous gaps.\n\nStoring `BuildContext` for later usage can easily lead to difficult to diagnose\ncrashes. Asynchronous gaps are implicitly storing `BuildContext` and are some of\nthe easiest to overlook when writing code.\n\nWhen a `BuildContext` is used, a `mounted` property must be checked after an\nasynchronous gap, depending on how the `BuildContext` is accessed:\n\n* When using a `State`'s `context` property, the `State`'s `mounted` property\n must be checked.\n* For other `BuildContext` instances (like a local variable or function\n argument), the `BuildContext`'s `mounted` property must be checked.\n\n**BAD:**\n```dart\nvoid onButtonTapped(BuildContext context) async {\n await Future.delayed(const Duration(seconds: 1));\n Navigator.of(context).pop();\n}\n```\n\n**GOOD:**\n```dart\nvoid onButtonTapped(BuildContext context) {\n Navigator.of(context).pop();\n}\n```\n\n**GOOD:**\n```dart\nvoid onButtonTapped(BuildContext context) async {\n await Future.delayed(const Duration(seconds: 1));\n\n if (!context.mounted) return;\n Navigator.of(context).pop();\n}\n```\n\n**GOOD:**\n```dart\nabstract class MyState extends State {\n void foo() async {\n await Future.delayed(const Duration(seconds: 1));\n if (!mounted) return; // Checks `this.mounted`, not `context.mounted`.\n Navigator.of(context).pop();\n }\n}\n```\n", "sinceDartSdk": "2.13.0" }, { diff --git a/src/tools/diagnostic-messages.md b/src/tools/diagnostic-messages.md index 1f340be740..be07dd8c8c 100644 --- a/src/tools/diagnostic-messages.md +++ b/src/tools/diagnostic-messages.md @@ -1918,6 +1918,90 @@ extension [!mixin!] on int {} Choose a different name for the declaration. +### callback_must_not_use_typed_data + +_FFI callbacks can't take typed data arguments or return value._ + +#### Description + +The analyzer produces this diagnostic when an invocation of +`Pointer.fromFunction`, one of`NativeCallable`'s constructors has a +typed data argument or return value." + +Typed data unwrapping is only supported on arguments for leaf FFI calls. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the parameter type +of `g` is a typed data. + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; +import 'dart:typed_data'; + +void f(Uint8List i) {} + +void g() { + Pointer.fromFunction)>([!f!]); +} +{% endprettify %} + +#### Common fixes + +Use the `Pointer` type instead: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +void f(Pointer i) {} + +void g() { + Pointer.fromFunction)>(f); +} +{% endprettify %} + +### call_must_not_return_typed_data + +_FFI calls can't return typed data._ + +#### Description + +The analyzer produces this diagnostic when the return type of +`Pointer.asFunction`, `DynamicLibrary.lookupFunction`, or +`@Native` is a typed data. + +Typed data unwrapping is only supported on arguments for leaf FFI calls. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the dart function +signature contains a typed data, but the `isLeaf` argument is `false`: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; +import 'dart:typed_data'; + +void f(Pointer Function()>> p) { + p.asFunction<[!Uint8List Function()!]>(); +} +{% endprettify %} + +#### Common fixes + +Use the `Pointer` type instead: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +void f(Pointer Function()>> p) { + p.asFunction Function()>(); +} +{% endprettify %} + ### case_block_not_terminated _The last statement of the 'case' should be 'break', 'continue', 'rethrow', @@ -8105,7 +8189,7 @@ has `Enum` in the `on` clause, declares an explicit operator named `==`: {% prettify dart tag=pre+code %} mixin M on Enum { - bool operator [!==!](Object? other) => false; + bool operator [!==!](Object other) => false; } {% endprettify %} @@ -13975,6 +14059,159 @@ Replace the value with a string: name: example {% endprettify %} +### native_field_invalid_type + +_'{0}' is an unsupported type for native fields. Native fields only support +pointers or numeric and compound types._ + +#### Description + +The analyzer produces this diagnostic when an `@Native`-annotated field +has a type not supported for native fields. + +Array fields are unsupported because there currently is no size +annotation for native fields. It is possible to represent global array +variables as pointers though, as they have an identical representation in +memory. + +Handles are unsupported because there is no way to transparently load and +store Dart object into pointers. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the field `f` uses an +unsupported type, `Array`: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +@Native() +external Array [!f!]; +{% endprettify %} + +#### Common fixes + +For array fields, use a pointer instead: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +@Native() +external Pointer f; +{% endprettify %} + +### native_field_missing_type + +_The native type of this field could not be inferred and must be specified in +the annotation._ + +#### Description + +The analyzer produces this diagnostic when an `@Native`-annotated field +requires a type hint on the annotation to infer the native 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 the correct load and stores when accessing the field, an +explicit type must be given. + +#### Example + +The following code produces this diagnostic because the field `f` has +the type `int` (for which multiple native representations exist), but no +explicit type parameter on the `Native` annotation: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +@Native() +external int [!f!]; +{% endprettify %} + +#### Common fixes + +To fix this diagnostic, find out the correct native representation from +the native declaration of the field. Then, add the corresponding type to +the annotation. For instance, if `f` was declared as an `uint8_t` in C, +the Dart field should be declared as: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +@Native() +external int f; +{% endprettify %} + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +### native_field_not_static + +_Native fields must be static._ + +#### Description + +The analyzer produces this diagnostic when an instance field in a class +has been annotated with `@Native`. +Native fields refer to global variables in C, C++ or other native +languages, whereas instance fields in Dart are specific to an instance of +that class. Hence, native fields must be static. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the field `f` in the +class `C` is `@Native`, but not `static`: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +class C { + @Native() + external int [!f!]; +} +{% endprettify %} + +#### Common fixes + +Either make the field static: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +class C { + @Native() + external static int f; +} +{% endprettify %} + +Or move it out of a class, in which case no explicit `static` modifier is +required: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +class C { +} + +@Native() +external int f; +{% endprettify %} + +If you meant to annotate an instance field that should be part of a +struct, omit the `@Native` annotation: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; + +final class C extends Struct { + @Int() + external int f; +} +{% endprettify %} + ### new_with_undefined_constructor_default _The class '{0}' doesn't have an unnamed constructor._ @@ -15167,6 +15404,52 @@ class A { class B implements A {} {% endprettify %} +### non_leaf_call_must_not_take_typed_data + +_FFI non-leaf calls can't take typed data arguments._ + +#### Description + +The analyzer produces this diagnostic when the value of the `isLeaf` +argument of `Pointer.asFunction`, `DynamicLibrary.lookupFunction`, or +`@Native` is `false` and the Dart function signature contains a typed +data parameter. + +Typed data unwrapping is only supported on arguments for leaf FFI calls. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the dart function +signature contains a typed data, but the `isLeaf` argument is `false`: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; +import 'dart:typed_data'; + +void f(Pointer)>> p) { + p.asFunction<[!void Function(Uint8List)!]>(); +} +{% endprettify %} + +#### Common fixes + +If the function has at least one typed data parameter, then add +the `isLeaf` argument: + +{% prettify dart tag=pre+code %} +import 'dart:ffi'; +import 'dart:typed_data'; + +void f(Pointer)>> p) { + p.asFunction(isLeaf: true); +} +{% endprettify %} + +If the function also uses `Handle`s, then it must be non-leaf. In That +case use `Pointer`s instead of typed data. + ### non_native_function_type_argument_to_pointer _Can't invoke 'asFunction' because the function signature '{0}' for the pointer