Skip to content

Commit

Permalink
Update diagnostic messages and linter rules (#5419)
Browse files Browse the repository at this point in the history
  • Loading branch information
parlough authored Dec 20, 2023
1 parent 48dcda3 commit 4270c61
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 4 deletions.
6 changes: 3 additions & 3 deletions src/_data/linter_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand All @@ -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"
},
{
Expand Down Expand Up @@ -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<MyWidget> {\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"
},
{
Expand Down
285 changes: 284 additions & 1 deletion src/tools/diagnostic-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void Function(Pointer<Uint8>)>([!f!]);
}
{% endprettify %}

#### Common fixes

Use the `Pointer` type instead:

{% prettify dart tag=pre+code %}
import 'dart:ffi';

void f(Pointer<Uint8> i) {}

void g() {
Pointer.fromFunction<Void Function(Pointer<Uint8>)>(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<NativeFunction<Pointer<Uint8> 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<NativeFunction<Pointer<Uint8> Function()>> p) {
p.asFunction<Pointer<Uint8> Function()>();
}
{% endprettify %}

### case_block_not_terminated

_The last statement of the 'case' should be 'break', 'continue', 'rethrow',
Expand Down Expand Up @@ -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 %}

Expand Down Expand Up @@ -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<Int> [!f!];
{% endprettify %}

#### Common fixes

For array fields, use a pointer instead:

{% prettify dart tag=pre+code %}
import 'dart:ffi';

@Native()
external Pointer<Int> 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<Uint8>()
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<Int>()
external int [!f!];
}
{% endprettify %}

#### Common fixes

Either make the field static:

{% prettify dart tag=pre+code %}
import 'dart:ffi';

class C {
@Native<Int>()
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<Int>()
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._
Expand Down Expand Up @@ -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<NativeFunction<Void Function(Pointer<Uint8>)>> 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<NativeFunction<Void Function(Pointer<Uint8>)>> p) {
p.asFunction<void Function(Uint8List)>(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
Expand Down

0 comments on commit 4270c61

Please sign in to comment.