Hey all,
I've run into a funny issue. What I'm trying to accomplish: Using Generics within a Type. I might be approaching this the wrong way!
What I'd like to be able to do, but doesn't work:
// From Flutter
class StoreProvider<S> {
factory StoreProvider.of() {
return (context.inheritFromWidgetOfExactType(StoreProvider<S>) as StoreProvider<S>)
.store
}
}
I have a class like this to get it working:
class StoreFinder<S> {
Store<S> of(BuildContext context) {
// Use the class' capture S type to create a new StoreProvider with the correct generic and capture the type
final type = new StoreProvider<S>().runtimeType;
return (context.inheritFromWidgetOfExactType(type) as StoreProvider<S>);
}
}
The problem: If I have the following class and call it like so: new StoreCaptor<String>(), everything works!
// ignore: must_be_immutable
class StoreCaptor<S> extends StatelessWidget {
static const Key captorKey = const Key("StoreCaptor");
Store<S> store;
StoreCaptor() : super(key: captorKey);
@override
Widget build(BuildContext context) {
store = new StoreFinder<S>().of(context);
return new Container();
}
}
If I have a class like this (with one extra generic), it fails: new StoreCaptor<String, String>()
// ignore: must_be_immutable
class StoreCaptor<S, A> extends StatelessWidget {
static const Key captorKey = const Key("StoreCaptor");
Store<S> store;
StoreCaptor() : super(key: captorKey);
@override
Widget build(BuildContext context) {
store = new StoreFinder<S>().of(context);
return new Container();
}
}
However! If I slightly change the second StoreCaptor class called in the same way to use A as the generic instead of S, everything works?
// ignore: must_be_immutable
class StoreCaptor<S, A> extends StatelessWidget {
static const Key captorKey = const Key("StoreCaptor");
Store<A> store;
StoreCaptor() : super(key: captorKey);
@override
Widget build(BuildContext context) {
// StoreFinder works fine and gives me back a Store!
store = new StoreFinder<A>().of(context);
return new Container();
}
}
I have no idea why, seems really odd that simply changing the order of the generics in the class will cause my code to work :( My class that calls StoreFinder needs to support 2 generics, so I can't simply support one generic. In addition, changing those params around would be a bit weird from an API perspective, so I'd like to keep them in their current order.
What Dart/Flutter version are you using?
If I have a class like this (with one extra generic), it fails:
new StoreCaptor<String, String>()
@brianegan your code under this line says:
// StoreFinder works fine and gives me back a Store!
store = new StoreFinder<S>().of(context);
Should it instead say StoreFinder _does not work_ and _does not_ give me back a Store!?
@mraleph Indeed it should. Meant only for the second example. Thanks for catching that!
This was on Dart 2.0.0-edge.0d5cf900 from the Flutter beta channel.
I can't see anything obvious in the example, so I'll just give you a different workaround.
You can't write:
Type x = StoreFinder<S>;
but you can use a helper class (or in Dart 2, even a helper function) to get that type:
class _TypeOf<T> { Type get type => T; }
Type _typeOf<T>() => T;
...
Type x = new _TypeOf<StoreProvider<S>>().type;
Type y = _typeOf<StoreProvider<S>>();
That might be easier than creating an entire extra class, doing instantiation and using runtimeType (never use runtimeType, it's bad for you!)
With that, you can write:
factory StoreProvider.of() {
Type storeProviderType = _typeOf<StoreProvider<S>>(): // or use the class in Dart 1.
return (context.inheritFromWidgetOfExactType(storeProviderType) as StoreProvider<S>)
.store
}
which is almost what you originally wanted.
Thanks @lrhn, I'll give that a shot and report back! I was definitely trying to avoid runtimeType but couldn't see a workaround :)
@lrhn Worked like a charm. I'll go with that approach, thanks so much!
General Q: This feels like a bit of a workaround, will something like this be supported in Future<Dart>?
Reduction
class A<T> { }
final map = {};
class B<T> {
void foo() {
print(map[new A<T>().runtimeType]);
}
}
class C<T, U> {
void build() {
new B<T>().foo();
new B<U>().foo();
}
}
void main() {
map[new A<String>().runtimeType] = 42;
new C<String, String>().build();
}
$ pkg/vm/tool/dart2 /tmp/x.dart
null
42
@jensjoha or @crelier: could one of you take a look if you have time?
General Q: This feels like a bit of a workaround, will something like this be supported in Future
?
I hope so. I intend so. Let's see if I succeed :)
Yay! If not, maybe even a global helper / standard pattern for this type of thing would be great.
@mraleph Thanks for reducing it to a good test case :)
Thanks for the report.
This is the case of an optimization coming back to bite us.
The VM runtime is reusing type argument vectors when possible, to avoid reallocation, even if the vector happens to be longer than needed. Extra type arguments in the vector are simply ignored (well, not always, that's the bug).
This optimization applies on this line:
new B
The vector
The new instance of B shares the type argument vector of its instantiator and ignores the second type argument.
This optimization does not apply on this line, however:
new B().foo();
because
Now, the problem happens when runtimeType canonicalizes the types. Canonicalization fails to ignore the extra type argument and two different "canonical" types exists for A
I'll work on a fix and ensure that type canonicalization is reallocating vectors with extra arguments.
Note that the native call Object_haveSameRuntimeType has the same bug. I'll fix it too.
CL is still waiting for a review: https://dart-review.googlesource.com/c/sdk/+/45340
Thanks for the fast response and fix :)
You are welcome. Thanks for the report.
@lrhn Do you all know if this Type _typeOf<T>() => T; is fully working in Dart 2? Are there bugs around this?
I changed my code to using Generic Types on Methods / Functions when I upgraded my lib to support Dart 2, and since I've had a ton of bug reports on my repo using this technique. Using the class solution class _TypeOf<T> { Type get type => T; } seems to fix the problem for some of my users. Is the generic methods / functions feature complete and working? Should we rely on it?
I've got a test suite that relies on this method and it works, but for others it doesn't.
In Dart 2, Type _typeOf<T>() => T; will definitely return the actual type argument of _typeOf as an instance of Type, but the question is to which extent your actual tool chain implements Dart 2 at this point in time, and going ahead. The support in the vm for reification of the actual arguments of generic function invocations has been controlled by the flag --reify-generic-functions for a while, so one thing you could try would be to use that.
@eernstg Thanks for the heads up! I'll try that out.
Do you happen to know if the --refiy-generic-functions option is enabled by default when the --preview-dart-2 flag is added?
--preview-dart-2 is intended to subsume --reify-generic-functions at some point, but it might not have happened yet. It's a matter of days and exact version numbers...
@eernstg Thanks much -- appreciate the info!
From the example above, calling this code crashes the app in Android.
Flutter version
Flutter 0.3.0 • channel dev • https://github.com/flutter/flutter.git
Framework • revision c73b8a7cf6 (7 days ago) • 2018-04-12 16:17:26 -0700
Engine • revision 8a6e64a8ef
Tools • Dart 2.0.0-dev.47.0.flutter-4126459025
Reproduce
Type y = _typeOf<StoreProvider<S>>();
Logs
E/DartVM (29011): ../../third_party/dart/runtime/vm/object.cc: 16408: error: unreachable code
E/DartVM (29011): Dumping native stack trace for thread 7164
E/DartVM (29011): [0x0000785da24a7f67] Unknown symbol
E/DartVM (29011): [0x0000785da24a7f67] Unknown symbol
E/DartVM (29011): [0x0000785da2743786] Unknown symbol
E/DartVM (29011): -- End of DumpStackTrace
F/libc (29011): Fatal signal 6 (SIGABRT), code -6 in tid 29028 (Thread-2)
Build fingerprint: 'Android/sdk_google_phone_x86_64/generic_x86_64:7.0/NYC/4662066:userdebug/dev-keys'
Revision: '0'
ABI: 'x86_64'
pid: 29011, tid: 29028, name: Thread-2 >>> com.example.helloworld <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
rax 0000000000000000 rbx 0000785da166e4f8 rcx ffffffffffffffff rdx 0000000000000006
rsi 0000000000007164 rdi 0000000000007153
r8 0000000000000000 r9 000000000000001f r10 0000000000000008 r11 0000000000000206
r12 0000000000007164 r13 0000000000000006 r14 0000785da27ac837 r15 0000785da1666bd0
cs 0000000000000033 ss 000000000000002b
rip 0000785dbd97fb67 rbp 0000000000000000 rsp 0000785da1666a38 eflags 0000000000000206
backtrace:
@brianegan, it would be great if you could provide a standalone reproduction, something like:
$ cat ~/bug.dart
Type _typeOf<T>() => T;
class StoreProvider<S> {
foo() {
Type y = _typeOf<StoreProvider<S>>();
print(y);
}
}
main() {
new StoreProvider<bool>().foo();
}
This example does not crash:
$ pkg/vm/tool/dart2 ~/bug.dart
StoreProvider<bool>
@crelier Hey there -- sorry, I should have updated this thread. I think the problem was perhaps related to what eernstg mentioned above: whether the reified generic functions feature was enabled or not for certain folks / toolchains. I haven't gotten more bug reports with the latest version of the Flutter beta.
This code does not fail on startup, but during hot reload.
//-------------------------------------------------------------------
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final String title;
MyApp({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Redux Demo",
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class AppState {
}
class Provider<T> {
}
abstract class PageState<W extends StatefulWidget, S> extends State<W> {
// Workaround to capture generics
static Type _typeOf<T>() => T;
void init() {
final type1 = _typeOf<Provider<AppState>>(); // works
final type2 = _typeOf<Provider<S>>(); // DOES NOT WORK - object.cc: 16408: error: unreachable code
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends PageState<MyHomePage, AppState> {
@override
Widget build(BuildContext context) {
super.init(); // calls into method that causes failure
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
),
),
);
}
}
// -----------------------------------------------------------------
Initializing hot reload...
E/DartVM ( 3758): ../../third_party/dart/runtime/vm/object.cc: 16408: error: unreachable code
E/DartVM ( 3758): Dumping native stack trace for thread ec6
E/DartVM ( 3758): [0x0000736d9c49df67] Unknown symbol
E/DartVM ( 3758): [0x0000736d9c49df67] Unknown symbol
E/DartVM ( 3758): [0x0000736d9c739786] Unknown symbol
E/DartVM ( 3758): -- End of DumpStackTrace
F/libc ( 3758): Fatal signal 6 (SIGABRT), code -6 in tid 3782 (Thread-2)
Build fingerprint: 'Android/sdk_google_phone_x86_64/generic_x86_64:7.0/NYC/4662066:userdebug/dev-keys'
Revision: '0'
ABI: 'x86_64'
pid: 3758, tid: 3782, name: Thread-2 >>> com.example.helloworld <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
rax 0000000000000000 rbx 0000736d9b5654f8 rcx ffffffffffffffff rdx 0000000000000006
rsi 0000000000000ec6 rdi 0000000000000eae
r8 0000000000000000 r9 000000000000001f r10 0000000000000008 r11 0000000000000202
r12 0000000000000ec6 r13 0000000000000006 r14 0000736d9c7a2837 r15 0000736d9b55dbd0
cs 0000000000000033 ss 000000000000002b
rip 0000736db7c93b67 rbp 0000000000000000 rsp 0000736d9b55da38 eflags 0000000000000202
backtrace:
#00 pc 000000000008db67 /system/lib64/libc.so (tgkill+7)
#01 pc 000000000008a601 /system/lib64/libc.so (pthread_kill+65)
#02 pc 0000000000030241 /system/lib64/libc.so (raise+17)
#03 pc 000000000002877d /system/lib64/libc.so (abort+77)
#04 pc 000000000078c695 /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#05 pc 0000000000a8078a /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#06 pc 0000000000750ed2 /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#07 pc 0000000000704cf6 /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#08 pc 000000000071e48f /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#09 pc 000000000082ea2c /data/app/com.example.helloworld-1/lib/x86_64/libflutter.so
#10 pc 000000000000063a
#11 pc 0000000000020fa0
#12 pc 0000000000020840
#13 pc 0000000000002233
#14 pc 0000000000008523
#15 pc 000000000000768c
#16 pc 0000000000006f98
#17 pc 000000000003f2c6
#18 pc 000000000000687f
#19 pc 00000000000054b2
#20 pc 00000000000046af
#21 pc 000000000000887b
#22 pc 000000000000768c
#23 pc 0000000000006f98
#24 pc 000000000000687f
#25 pc 00000000000054b2
#26 pc 00000000000046af
#27 pc 0000000000031077
#28 pc 00000000000054b2
#29 pc 00000000000046af
#30 pc 0000000000031077
#31 pc 00000000000054b2
#32 pc 00000000000046af
#33 pc 0000000000031077
#34 pc 00000000000054b2
#35 pc 00000000000046af
#36 pc 0000000000031077
#37 pc 00000000000054b2
#38 pc 00000000000046af
#39 pc 000000000000887b
#40 pc 000000000000768c
#41 pc 0000000000006f98
#42 pc 000000000003f2c6
#43 pc 000000000000687f
#44 pc 00000000000054b2
#45 pc 00000000000046af
#46 pc 000000000000887b
#47 pc 000000000000768c
#48 pc 0000000000006f98
#49 pc 000000000000687f
#50 pc 00000000000054b2
#51 pc 00000000000046af
#52 pc 000000000000887b
#53 pc 000000000000768c
#54 pc 0000000000006f98
#55 pc 000000000003f2c6
#56 pc 000000000000687f
#57 pc 00000000000054b2
#58 pc 00000000000046af
#59 pc 0000000000031077
#60 pc 00000000000054b2
#61 pc 00000000000046af
#62 pc 000000000000887b
#63 pc 000000000000768c
Lost connection to device.
@kyle-berry-perficient you can make code and log output easier to read, by wrapping it in ```
You can edit the existing comment and add it.
@a-siva, can someone familiar with hot-reload on flutter have a look? Thanks.
@crelier / @a-siva it looks like this issue has been marked as Closed, should I create a separate issue for the "../../third_party/dart/runtime/vm/object.cc: 16408: error: unreachable code" issue?
@affinnen can you please open a new issue?
@dgrove / @crelier / @a-siva - a new issue has been opened #32942.
Most helpful comment
Thanks for the report.().foo(); of class C (or more exactly its subvector of length 1, i.e. ) is compatible with the required vector of class A. does not overlap with at index 0.
This is the case of an optimization coming back to bite us.
The VM runtime is reusing type argument vectors when possible, to avoid reallocation, even if the vector happens to be longer than needed. Extra type arguments in the vector are simply ignored (well, not always, that's the bug).
This optimization applies on this line:
new B
The vector
The new instance of B shares the type argument vector of its instantiator and ignores the second type argument.
This optimization does not apply on this line, however:
new B().foo();
because
Now, the problem happens when runtimeType canonicalizes the types. Canonicalization fails to ignore the extra type argument and two different "canonical" types exists for A.
I'll work on a fix and ensure that type canonicalization is reallocating vectors with extra arguments.
Note that the native call Object_haveSameRuntimeType has the same bug. I'll fix it too.