Sound Dart: Fixing Common Problems

Send feedback

If you’re having problems converting your code to strong mode, this page can help. Be sure to also check out Sound Dart for an overview of what “sound Dart” means, and how strong mode contributes to making Dart a sound language.

Contents

Troubleshooting:

Common errors and warnings:

Appendix:

For a complete list of sources about strong mode and sound Dart, see other resources in Sound Dart.

Troubleshooting

Am I really using strong mode?

If you’re not seeing strong mode errors or warnings, make sure that you’re using strong mode. A good test is to add the following code to a file:

void test() {
  bool b = [0][0];
}

If you’re using strong mode, you’ll see the following warning from the analyzer:

[warning] A value of type 'int' can't be assigned to a variable of type 'bool'.

I’m not using strong mode and I think I should be

Strong mode is enforced by Dart Analyzer. How you troubleshoot strong mode depends on whether you are running dartanalyzer from the command line, or via one of the JetBrains IDEs.

Command line analyzer

If you are running dartanalyzer --strong and you don’t see expected strong mode errors, be sure that you didn’t disable strong mode in an analysis options file in the same directory where you run the analyzer. If you do, the analysis options file overrides your command line flag.

For more information on how to set up an analysis options file, see Customize Static Analysis.

JetBrains IDEs

Make sure that you have an analysis options file with strong mode turned on. This file needs to be placed in a content root, or in a parent directory of your content root. The steps for viewing a project’s content root varies a bit for WebStorm and IntelliJ.

Note that a large project may have multiple content roots. The following instructions describe how to see a list of content roots in WebStorm or IntelliJ.

WebStorm
In the Preferences panel (WebStorm > Preferences), click Directories from the list on the left. The +Add Content Root button in the column on the far right appears above the content roots, shown in bold.
IntelliJ
In the Project Structure panel (File > Project Structure), Modules is selected from the list on the left by default. The +Add Content Root button in the column on the far right appears above the content roots, shown in bold.

For more information on where to put your analysis options file, see the analysis options file, part of Customize Static Analysis.

Common errors and warnings

How to fix some of the errors and warnings you may see from the analyzer when enabling strong mode.

Undefined member

Error: <member> isn't defined for the class <class>

These errors usually appear in code where a variable is statically known to be some supertype but the code assumes a subtype.

Fix: Replace the definition of the member with an explicit type declaration or a downcast.

For example, the analyzer complains that context2D in the following code is undefined:

var canvas = querySelector("canvas");
canvas.context2D; // <-- Error.

The querySelector() method statically returns an Element, but the code assumes it returns the subtype CanvasElement where context2D is defined. The canvas field is declared as var which, in classic Dart, types it as dynamic and silences all errors. Strong mode Dart infers canvas to be an Element.

Fix this error with an explicit type declaration:

CanvasElement canvas = querySelector("canvas");
canvas.context2D;

If you actually want a dynamic type, specify dynamic:

dynamic canvas = querySelector("canvas");
canvas.context2D;

Invalid method override

Error: Invalid override. The type of <type> is not a subtype of <type>.

These errors typically occur when a subclass tightens up a method’s parameter types by specifying a subclass of the original class.

Fix: Widen the types in the method’s parameter list. The subclass’s method should accept every object that the superclass’s method takes.

In the following example, the parameters in the add() method are changed from num to int, a subtype of num. This code passes static analysis in classic Dart, but is unsafe and fails analysis in strong mode Dart.

abstract class NumberAdder {
  num add(num a, num b);
}

class IntAdder extends NumberAdder {
  int add(int a, int b) => a + b;
}

Consider the following scenario where floating point values are passed to an IntAdder:

NumberAdder adder = new IntAdder(); // Upcast
adder.add(1.2, 3.4);                // Kaboom!

If the override were allowed, this code would crash at runtime.

Fix this error by widening the types in the subclass:

abstract class NumberAdder {
  num add(num a, num b);
}

class IntAdder extends NumberAdder {
  num add(num a, num b) => a + b;
}

For more information, see Use proper input parameter types when overriding methods.


Unsound implicit downcast

Warning: Unsound implicit cast from Class<dynamic> to Class<type>.

Implicit downcasts involving dynamic will most likely fail at runtime in DDC, so the analyzer provides a warning.

Fix: Provide an explicit type, or give the analyzer enough information to properly infer the type.

For example, the following code generates the warning “Unsound implicit cast from List<dynamic> to List<String>”.

var stuff = []; // Runtime type is List<dynamic>.
stuff.add("Hi");
List<String> strings = stuff;

The best way to fix this it to give the analyzer enough information to correctly infer the list’s type:

var stuff = ['Hi']; // Runtime type is List<String>.
List<String> strings = stuff;

You could also explicitly specify the list’s type:

var stuff = <String>[]; // Runtime type is List<String>.
stuff.add("Hi");
List<String> strings = stuff;

As a last resort, you can cast the type using as Class. This solution is risker because the cast silences the static error by inserting a runtime cast that may fail at runtime.

var stuff = []; // Runtime type is List<dynamic>.
stuff.add("Hi");
List<String> strings = stuff as List<String>;

In more complex situations where this warning appears, you may want to use a generic method. You can either use an existing method, such as Iterable.map(), or you can define your own. For more information, see Using generic methods in the language tour.


Missing type arguments

Omitting a type argument when defining a generic subclass can cause one of two kinds of problems during static analysis:

Error: Invalid override. The type of <type> is not a subtype of <type>.

or

Warning: Unsound implicit cast from Class<dynamic> to Class<type>.

Fix: Specify type arguments for the generic subclass.

When a generic subclass neglects to specify a type argument, the analyzer infers the dynamic type. This is likely to cause errors like invalid overrides or unsound downcasts.

In the following example, Subclass extends Superclass<T> but doesn’t specify a type argument. The analyzer infers Subclass<dynamic>, which results in an invalid override error on method(int).

class Superclass<T> {
  void method(T t) {}
}

class Subclass extends Superclass {
  void method(int i) {} // <-- Error.
}

You can fix this by specifying the type on the subclass:

class Superclass<T> {
  void method(T t) {}
}

class Subclass extends Superclass<int> {
  void method(int i) {}
}

Assigning mismatched types

Warning: A value of type '<type>' cannot be assigned to a variable of type 'type'.

This sometimes happens when you create a simple dynamic collection and the analyzer Dart infers the type in a way you didn’t expect. When you later add values of a different type, the analyzer produces a warning.

Fix: Specify the type explicitly.

For example, the following code initializes a map with several (String, integer) pairs. The analyzer infers that map to be of type <String, int> but the code assumes <String, dynamic>. When the code then adds a (String, float) pair, the analyzer complains.

void main() {
  var map = {
    'a': 7,
    'b': 11,
    'c': 13
  }; // <= inferred to be Map<String, int>

  map['d'] = 1.5;  // 1.5 is not int!
}

This can be fixed by explicitly defining the map’s type to be <String, dynamic>.

void main() {
  var map = <String, dynamic>{
    'a': 7,
    'b': 11,
    'c': 13
  };

  map['d'] = 1.5;
}

Alternatively, if you only want this map to accept integers and floats, you can specify the type as <String, num>.


Constructor initialization list super() call

Error: super call must be last in an initializer list (see https://goo.gl/EY6hDP): 'super(style)'.

This error occurs when the super() call is not last in a constructor’s initialization list.

Fix: Put the super() call last.

DDC generates simpler code if it relies on the super() call appearing last. The following example generates an error in strong mode Dart:

HoneyBadger(Eats food, String name)
  : super(food),
    _name = name { ... }

Fix the error by moving the super() call:

HoneyBadger(Eats food, String name)
  : _name = name,
    super(food) { ... }

For more information, see DO place the super() call last in a constructor initialization list in Effective Dart.


Appendix

The @checked annotaton

Some (rarely used) coding patterns rely on tightening a type by overriding a parameter’s type with a subtype, which is illegal in strong mode Dart. In this case, you can use the @checked annotation to tell the analyzer that you are doing this intentionally. This removes the static error and instead checks for an invalid parameter type at runtime.

The following shows how you might use @checked:

import 'package:meta/meta.dart';

class Animal {
  void chase(Animal x) {}
}

class Mouse extends Animal {}

class Cat extends Animal {
  void chase(@checked Mouse x) {}
}

Although this example shows using @checked in the subtype, the @checked annotation can be placed in either the superclass or the subclass method. Usually the superclass method is the best place to put it. The @checked annotation applies to a single parameter and is also supported on setters and fields.