Skip to content

Introduce a strict-raw-types static inference mode. #516

Closed
@srawlins

Description

@srawlins

The goal is to catch more error-prone code during static analysis; for example, code which is guaranteed to error at runtime. This often two secondary effects: (1) reducing errors in developer understanding of such code, and (2) reducing dynamic dispatch in code where a developer did not think dynamic dispatch was occurring.

We define a "raw type" as a type with omitted type argument(s). Dart fills in such types with their bounds, or dynamic if there are no bounds. Here are some examples:

void main() {
  List a = [1, 2, 3];
  a.forEach((n) => print(n.isEven));
  fn(a);
}

void fn(List strings) => strings.forEach((s) => print(s.toUpperCase()));

On the first line of main, a is declared as a List<dynamic>. The type of [1, 2, 3] is inferred from List<dynamic>. There is no implicit cast. On the second line, n.isEven is a dynamic dispatch call, since the elements of a are treated as dynamic. On the third line, when fn is called, the runtime will throw a NoSuchMethodError, trying to call toUpperCase on the numbers. There are currently no compile-time, static analysis checks that would reveal a problem.

The proposed "strict-raw-types" mode will report errors (e.g. Hints in the analyzer) at the declaration of most types with omitted type arguments. Here are some examples of "raw types" that would be reported:

  • A variable declaration, like List a = [1, 2, 3];
  • A type argument, like Future<List> a;
  • A function parameter, like main(List args) {}, typedef cb = void Function(Future)
  • A function return type, like Future main() {}, typedef cb = Future Function()
  • A class declaration, like class MyList extends List {}, mixin Foo on List

There are some cases where a type will not be considered a "raw type":

  • Omitted type arguments on types which are annotated with @optionalTypeArgs from the meta package. There are some classes which are often used in ways where type arguments are not known statically, or they are found in heterogeneous collections. If such a class is annotated with @optionalTypeArgs, then it will not be reported as a "raw type."
  • A type on the right side of an is or as expression; in experiments it was found that this just forced developers to write is List<dynamic> and as Map<dynamic, dynamic> over and over and over. This increases verbosity, without improving correctness or understandability.

Raw types are currently one type of static analysis report found with the "implicit-dynamic: false" static analysis mode. However, multiple attempts to enable this mode on large code bases have shown it to be too onerous to comply with; "strict raw types" are just one piece of "inplicit-dynamic: false", and should be must easier to comply with.

This issue is a continuation of dart-lang/sdk#33749. Thanks to @munificent, @leafpetersen, and @matanlurey for specifying most of the above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions