Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
srawlins opened this issue Aug 11, 2019 · 0 comments · Fixed by #521
Closed

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

srawlins opened this issue Aug 11, 2019 · 0 comments · Fixed by #521

Comments

@srawlins
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant