dart-lang / linter

Linter for Dart.
https://dart.dev/tools/linter-rules
BSD 3-Clause "New" or "Revised" License
628 stars 170 forks source link

proposal: `loss_of_type_information ` #4956

Closed Tomburgs closed 5 months ago

Tomburgs commented 5 months ago

loss_of_type_information

Alternatively loss_of_type_information_in_state would be more explicit.

Description

State implementation did not pass generics back to StatefulWidget declaration

Details

It would be beneficial to have a linter rule that catches errors related to the loss of type information in generic class definitions, especially in the context of Flutter's Stateful Widgets. This issue often arises when the state class does not properly specify the generic type on the State class, leading to potential type safety issues at run time.


For example, the following code would cause a runtime error when using the onTap method. Something along the lines of type '(MyConcreteTypeImplementation) => void' is not a subtype of type '(dynamic) => void'.

class MyGenericWidget<T> extends StatefulWidget {
  final T item;
  final void Function(T) onTap;

  // Imagine a constructor here

  @override
  _MyGenericWidgetState createState() => _MyGenericWidgetState<T>();
}

class _MyGenericWidgetState<T> extends State<MyGenericWidget> {
  // Imagine there is a widget here that calls the onTap with a generic type T.
}

This is because when declaring the _MyGenericWidgetState the generic T is not passed to MyGenericWidget. In the given example, it would have to be declared as:

class _MyGenericWidgetState<T> extends State<MyGenericWidget<T>>

Kind

This would be a flutter rule that would guard against runtime errors. Example of an error an incorrect implementation could cause: type '(MyConcreteTypeImplementation) => void' is not a subtype of type '(dynamic) => void'

Bad Examples

This should fire in the following example, because MyGenericWidget and _MyGenericWidgetState both accept the same generic (implies that it is passed on), however in the extends State<MyGenericWidget> the generic T is not passed on.

class MyGenericWidget<T> extends StatefulWidget { ... }
class _MyGenericWidgetState<T> extends State<MyGenericWidget> { ... }

Good Examples

The correct implementation of this rule would expect the generic to be passed on to the State<MyGenericWidget>.

class MyGenericWidget<T> extends StatefulWidget { ... }
class _MyGenericWidgetState<T> extends State<MyGenericWidget<T>> { ... }

Discussion

In my opinion this is extremely easy to overlook and can cause a lot of confusion, especially to newer flutter developers who have not ran into this exact issue before. To back this point, it is not difficult to find issues / StackOverflow questions asking about this exact issue.

Some of the aforementioned issues:

I don't believe such rule would overlap with any other rules. I've given the existing rules a look and could not find anything similar. Granted I'm not all that knowledgable in the dart linter workings, so feel free to comment / correct this if you, the reader, think it does overlap with something.

Tomburgs commented 5 months ago

previously opened in flutter: https://github.com/flutter/flutter/issues/147369

srawlins commented 5 months ago

I think what you're looking for is static analysis that reports 'raw types'. You can enable 'strict-raw-types' as seen here: https://dart.dev/tools/analysis#enabling-additional-type-checks