$ dart --version
Dart SDK version: 2.10.0-7.2.beta (beta) (Mon Aug 17 11:01:01 2020 +0200) on "windows_x64"
Some(foo)
Unhandled exception:
type '() => Option<String>' is not a subtype of type '() => Option<Null>' of 'ifNone'
#0 Option.orElse (package:zowo_lib/option_bug.dart)
#1 main (package:zowo_lib/option_bug.dart:27:27)
#2 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
import 'dart:io';
class Option<A> {
final A unsafeGet;
const Option.some(this.unsafeGet);
const Option.none() : unsafeGet = null;
@override String toString() => isSome ? "Some($unsafeGet)" : "None";
bool get isSome => unsafeGet != null;
Option<A> orElse(Option<A> Function() ifNone) => isSome ? this : ifNone();
}
Option<A> Some<A>(A a) => Option.some(a);
extension MapExts<K, V> on Map<K, V> {
Option<V> get(K key) => containsKey(key) ? Some(this[key]) : const Option.none();
}
final map = {"foo": "bar"};
void main() {
final noneStr = map.get("baz");
final someStr = noneStr.orElse(() => map.get("foo"));
stdout.write(someStr);
}
The problem is const Option.none(). Since it's constant, it can't use the V type parameter which would depend on the map you're passing to get. In your case, the type parameter of const Option.none() is inferred to be Null. You can verify this by printing noneStr.runtimeType - it's Option<Null>.
Your function has a type of Option<String> Function(), which is not a subtype of the Option<Null> Function() that would be expected by Option<Null>.orElse(). So, you're getting a type error at runtime. You can fix this by simply removing the const keyword in MapExts.
Thanks for clarifying.
Though I would still say that it is highly unintuitive and seems like it is leaking implementation details of the VM into the userspace code.
It is very confusing when the code compiles and says "yup, this will be Option<A>, no worries" and fails in runtime. I guess one can reason about it once you know how Dart does things under the hood. Personally I would rather emit a compilation error in the get function telling me that const isn't allowed here.
Turns out it does complain if you specify type arguments explicitly...

It's just that Option<Null> is assignable to Option<V>. Which according to https://dart.dev/faq#q-why-are-generics-covariant is "reasonable" (to which I disagree). Case closed I guess.
Thanks for the help!
Though I would still say that it is highly unintuitive
Agreed, this is a tricky error to spot (even for very experienced Dart developers, see https://github.com/google/quiver-dart/commit/41b4f6937d566228178b609fb0c8c7724535f583 for instance).
seems like it is leaking implementation details of the VM into the userspace code
This is not the VM, it's the Dart compiler behaving like specified. You'll get the same result with dart2js and ddc.
It's just that
Option<Null>is assignable toOption<V>
Time to :+1: https://github.com/dart-lang/language/issues/213 then :)
Most helpful comment
Agreed, this is a tricky error to spot (even for very experienced Dart developers, see https://github.com/google/quiver-dart/commit/41b4f6937d566228178b609fb0c8c7724535f583 for instance).
This is not the VM, it's the Dart compiler behaving like specified. You'll get the same result with dart2js and ddc.
Time to :+1: https://github.com/dart-lang/language/issues/213 then :)