Closed joranmulderij closed 9 months ago
Hi! 👋
If I’m understanding your error correctly, it’s because you must read a capsule with its correct type (and not let it get upcasted, either implicitly or explicitly). Thus, if reading myCapsule (from your example), you must either do:
final data = use(myCapsule); // data is inferred to be B
final A data = use<B>(myCapsule); // explicitly write type if we’re upcasting
use<SomeType>(someCapsule)
is also handy when writing use
in a list initializer with some super type.
This issue has been asked before, so I’ll keep this open until I can update the docs
I kind of feel like this should not happen, because the program fails on line 2 because of line 1. Also the resulting error is really obscure and it took me quite some time to figure out what was going on.
void later() {
final valueA = use(myCapsule as A); // If I would remove this line, the program will run correctly.
final valueB = use(myCapsule); // Will throw error.
}
final valueA = use(myCapsule as A); // If I would remove this line, the program will run correctly.
If this is copy-pasted directly from your code, this is where the error is originating from, not the next line. You cannot cast myCapsule
to A
(as it is a B Function(CapsuleHandle)
, aka a Capsule<B>
). You probably meant final valueA = use(myCapsule) as A;
(note the differing parentheses location).
If, however, you instead had use(myCapsule as Capsule<A>);
in your code, well then that would be an issue with how Dart assumes types and would be the same reason you only have to do someNullableVariable!
once. Take this code:
void typeTest(int? i) {
int b = i as int;
int c = i;
}
Notice how this compiles without needing a !
or another as int
after the second i
; this is because Dart uses its program analysis to prove that i
must be an int
after it is casted explicitly with as
. However, I think this is unlikely and just that you had the as A
in the wrong spot (inside the use()
instead of after).
Sorry, I wrote that code and did not test if it was correct.
This is a working example. The code below will throw an error on the valueB
line. When commenting the valueA
line it will complete successfully.
import 'package:rearch/rearch.dart';
class A {}
class B implements A {}
B myCapsule(CapsuleHandle use) {
final (value, setValue) = use.state(B());
return value;
}
void main() {
final container = CapsuleContainer();
final valueA = container.read(myCapsule as Capsule<A>); // Try commenting this line.
final valueB = container.read(myCapsule); // Will throw error
print(valueB);
}
Again, sorry for the confusion.
Thank you for pressing this a bit more! You unintentionally just enabled some new patterns and ergonomics that weren't possible before. I've got a fix locally and I also just realized that the fix will also what will enable cool patterns like [someCapsule, anotherCapsule].map(use).toList()
to work reliably.
Here's what's happening. When you container.read(myCapsule as Capsule<A>)
, the container is internally building an upcasted copy of your data and is storing that internal state with its upcasted type. When you go to read the same capsule out of the container, but this time with the actual type, it gets the cached internal state (already present in the container at this point since it was previously built) and then attempts to downcast it to the actual type, but this fails, since the internal state itself was made with the upcasted type.
You can reproduce this with the following test:
test('containers store capsules completely untyped (issue #36)', () {
// Re-reading the capsule under a different type should not throw.
() capsule(CapsuleHandle use) => ();
useContainer()
// ignore: unnecessary_cast
..read(capsule as Capsule<Object>)
..read(capsule);
});
Fix should be out 🔜
An example is the easiest: