dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.19k stars 1.57k forks source link

Better error message for runtime cast failures. #33092

Open srawlins opened 6 years ago

srawlins commented 6 years ago

Take the following code and error:

$ cat a.dart
import 'dart:async';

void main() {
  Stream<String> s = fn();
}

Stream fn() => new StreamController.broadcast().stream;

$ dart --preview-dart-2 a.dart
Unhandled exception:
type '_BroadcastStream<dynamic>' is not a subtype of type 'Stream<String>' where
  _BroadcastStream is from dart:async
  Stream is from dart:async
  String is from dart:core

#0      main (file:///google/src/cloud/srawlins/deprecated/google3/a.dart:4:22)
#1      _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
#2      _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)

Users are sometimes confused by this error. @matanlurey suggested something like

Type `_BroadcastStream<dynamic>`, which implements `Stream<dynamic>`, is not a subtype of 'Stream<String>' in strong mode. 
vsmenon commented 6 years ago

DDC now gives the error (as of @jmesserly 's recent change):

Type '_BroadcastStream<dynamic>' should be '_BroadcastStream<String>' to implement expected type 'Stream<String>'

Is that clearer? Filing this on VM as that's used in the example, but we should aim for some consistency.

srawlins commented 6 years ago

Ooh wow. I like that.

jmesserly commented 6 years ago

DDC's implementation basically uses (part of) generic type inference at runtime. Inference code was ported from CFE, and slimmed down to only the necessary bits. CL was https://dart-review.googlesource.com/c/sdk/+/54101, errors.dart (_castErrorMessage) and types.dart (_TypeInferrer) are the most interesting bits in that CL.

thosakwe commented 6 years ago

Is there any way that DDC could give some sort of stack trace/source map to where the offending code is? Sometimes it's not so obvious where the cast is.

jmesserly commented 6 years ago

Is there any way that DDC could give some sort of stack trace/source map to where the offending code is? Sometimes it's not so obvious where the cast is.

DDC could track object allocations, but it causes code to run extremely slow (every object allocation must capture a stack trace). Also the object allocation might not indicate the bug location, because that object type might've resulted from a type somewhere else (either due to type parameters or type inference).

Usually static checks are usually more helpful, e.g. --no-implicit-casts option which flags implicit downcasts. Or trying to look for places where type info was lost, and strengthen the type annotations. For example if there's a cast failure from Foo<dynamic> to Foo<String> the expression that has type Foo<dynamic> can be changed to Foo<String> (e.g. if it's a variable or a return value put a stronger type there), and then working backwards that way through the program until the cast failure is eliminated.

thosakwe commented 6 years ago

Thanks, I'll definitely try that!