New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
structural compatibility of union types / mutable union #2647
Comments
These properties are kind of mutually exclusive. If you want mutability then type refinement is not possible. |
Why would that be? Other languages with proper language-level unions/enums can also have internal mutability. |
@Swatinem any example? |
For example in rust: #[derive(Debug)]
enum U {
A(u32),
B(bool),
}
fn main() {
let mut u = U::A(42);
std::mem::replace(&mut u, U::B(true));
println!("{:?}", u);
} Ok, provided that rusts borrowck would complain if you had any references to that object, but there are also ways to deal with that. My point is that at least for the example in comment#1, the class properties are compatible with every one of the union variants, so mutating the discriminant should still yield a valid type. It does so at runtime, thats for sure. The problem here is more of what the programmers intent is. I could just use the plain class and access Like I said, I could just use a plain class, but that would not be sound anyway since flow does not enforce that properties are actually initialized, so |
I see. It probably should be possible if all properties in the union are covariant. One caveat though is that flow should invalidate refinement after function calls, because they can potentially mutate the type. To be fair, your example seems to be very specific to Rust. |
Ok, then let me elaborate more on a js example: type NotLoaded = {
loaded: false;
load: () => void;
};
type Loaded = {
loaded: true;
load: () => void;
content: string;
};
type ILoader = NotLoaded | Loaded;
class Loader {
loaded: boolean; // or `true | false` if you wanna be pedantic
content: string;
constructor() {
this.loaded = false;
}
load() {
this.content = "foobar";
this.loaded = true;
}
}
const l = new Loader();
l.content.length; // this will not warn at compile time but throw at runtime, because of #650
// which is a flaw in flow anyway, but lets ignore that for now. In practice you should define the prop anyway because of hidden class optimizations.
const il: ILoader = new Loader(); // <- this will warn at compile time, what this issue is about.
il.content.length; // this will warn at compile time and is exactly what I want!
il.load();
if (il.loaded) {
il.content.length; // I want this to only be valid inside the branch checking for `.loaded`!
} |
Right now when you assign a value to a union type, it needs to match at least one branch. I'm not sure that it's going to change |
BTW, here is your Rust example in Flow: type MutT<T> = { val: T };
function Mut<T>(val: T): MutT<T> {
return { val };
}
function replace<T>(mut: MutT<T>, val: T) {
mut.val = val;
}
type U =
| { t: 'A', v: number }
| { t: 'B', v: bool }
;
function A(v: number): U {
return { t: 'A', v };
}
function B(v: bool): U {
return { t: 'B', v };
}
const u = Mut(A(42));
replace(u, B(true)); |
Is this actually a feature request, or a question? Making |
Closing, since I am not interested in a solution here anymore. |
Consider the following code:
Flow says those types are not compatible with one another
While flow is right to say so, since the declared var is not compatible with either one of the variants. But it is compatible with both of them.
What I want to do is the following:
I want to have a union type that is mutable at runtime. I tried to do that via a class whos properties are the sum of all of the unions variants properties. But flow always says it is not compatible.
To elaborate further, consider this example code:
The problem is that I absolute need internal mutability / referencial integrity, via the class. But I also want to be typesafe on the outside, via the union type.
The text was updated successfully, but these errors were encountered: