flow-runtime
flow-runtime copied to clipboard
Support $ReadOnlyArray
This is a:
- [ ] Bug Report
- [x] Feature Request
- [ ] Question
- [ ] Other
Which concerns:
- [x] flow-runtime
- [ ] babel-plugin-flow-runtime
- [ ] flow-runtime-validators
- [ ] flow-runtime-mobx
- [ ] flow-config-parser
- [ ] The documentation website
type Thing = $ReadOnlyArray<string | number>;
const thing: Thing = [];
console.log(thing);
Output:
Thing must be an object
Expected: {
[Symbol(Symbol.iterator)]: () => Iterator<T>;
toLocaleString: () => string;
concat: <S, Item: $ReadOnlyArray<S> | S> (...items: Array<Item>) => Array<T | S>;
entries: () => Iterator<[number, T]>;
every: (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => boolean;
filter: ((callbackfn: Boolean) => Array<$NonMaybeType<T>>) | ((callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => Array<T>);
find: (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => T | void;
findIndex: (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => number;
forEach: (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => void;
includes: (searchElement: mixed, fromIndex?: number) => boolean;
indexOf: (searchElement: mixed, fromIndex?: number) => number;
join: (separator?: string) => string;
keys: () => Iterator<number>;
lastIndexOf: (searchElement: mixed, fromIndex?: number) => number;
map: <U> (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => U, thisArg?: any) => Array<U>;
reduce: ((callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: $ReadOnlyArray<T>) => T, initialValue: void) => T) | (<U> (callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: $ReadOnlyArray<T>) => U, initialValue: U) => U);
reduceRight: ((callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: $ReadOnlyArray<T>) => T, initialValue: void) => T) | (<U> (callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: $ReadOnlyArray<T>) => U, initialValue: U) => U);
slice: (start?: number, end?: number) => Array<T>;
some: (callbackfn: (value: T, index: number, array: $ReadOnlyArray<T>) => any, thisArg?: any) => boolean;
values: () => Iterator<T>;
length: number;
[key: number]: T;
}
Actual Value: []
Actual Type: Array<any>
IssueHunt Summary
Backers (Total: $40.00)
-
issuehunt ($40.00)
Submitted pull Requests
Become a backer now!
Or submit a pull request to get the deposits!
Tips
- Checkout the Issuehunt explorer to discover more funded issues.
- Need some help from other developers? Add your repositories on IssueHunt to raise funds.
IssueHunt has been backed by the following sponsors. Become a sponsor
@issuehunt has funded $40.00 to this issue.
- Submit pull request via IssueHunt to receive this reward.
- Want to contribute? Chip in to this issue via IssueHunt.
- Checkout the IssueHunt Issue Explorer to see more funded issues.
- Need help from developers? Add your repository on IssueHunt to raise funds.
@gajus any ideas how can covariance be supported in general? I can only think of Object.freeze
, but not sure if validators are allowed to do something like this to original values
I don't see a particular reason not to use Object.freeze. Since expectation is to prevent modification of array, this seems like a reasonable solution.
@gajus most babel-plugin-flow-runtime
assertions throw errors though; I think analogous support for $ReadOnlyArray
would wrap the runtime Array in a Proxy that throws if any modification is attempted, rather than just silently preventing modification; do you agree?
EDIT sorry, I forgot that Object.freeze
causes errors to be thrown if modification is attempted.
(In any case Object.freeze is better than nothing)
@gajus actually I think there's a problem. Say we pass an array from a context where it's intended to be writable to a context where it's intended to be read-only. Object.freeze
would prevent writes in the context where they're intended to work.
Dumb example but:
function logArray(arr: $ReadOnlyArray<number>): Array<number> {
console.log(arr)
}
const arr: Array<number> = [1, 2, 3]
logArray(arr)
arr.push(4) // whoops, throws if babel-plugin-flow-runtime is applied, due to Object.freeze?
logArray(arr)
I commented in the PR but I think making babel-plugin-flow-runtime
wrap the array with a proxy is the only solution that avoids the above problem. A lot more work to wrap the array in all cases $ReadOnlyArray
can be used though
@gajus actually I think there's a problem. Say we pass an array from a context where it's intended to be writable to a context where it's intended to be read-only. Object.freeze would prevent writes in the context where they're intended to work.
Why not just make a copy before object freeze?
const newArray = Object.freeze([...input]);
Then the original parameter is not impacted. And since the intention is to have it as read-only, seems reasonable to assume that modifying the array should not affect wherever it came from.
Problematic code
const foo: $ReadOnlyArray<number> = [1, 2, 3]
const bar: Array<string> = (foo: any); // bypassing typecheck, but array is still frozen
We can store original array alongside frozen one probably? Or make it work like function or class annotations
I mean... any
is um... you are shooting yourself in the foot anyway.
User can easily work around this with:
const foo: $ReadOnlyArray<number> = [1, 2, 3]
const bar: Array<string> = [
...foo
];
I thought that any
removes any type-assertions, but checked on site, this is not the case with functions or any other type
I think that supporting any
is a separate issue altogether. We could (as you mentioned) keep a copy of whatever the input is and revert it back to the original in case of any
. I think that supporting any
is pushing it to edge cases though.
@gajus there's also warning mode which wouldn't work with Object.freeze
, it would always throw
@gajus there's also warning mode which wouldn't work with Object.freeze, it would always throw
A sensitive thing to do would be to disable this in warning mode.
@gajus careful, copying the array causes problems too. $ReadOnlyArray
is just a read-only "view", Flow doesn't prevent the view from seeing changes to the array made externally later on, and I don't think it's safe for flow-runtime to do so either. Instead of copying it why not just wrap it in a proxy that blocks write operations?
Instead of copying it why not just wrap it in a proxy that blocks write operations?
My beef with wrapping it in proxy is that (correct me if I am wrong) but the Array.isArray
check will fail.
That's probably true, actually a problem with proxy or copy approach is that neither is === the original array
I think the only 100% solid approach is for the Babel plugin to add code to any index assignments, length assignments, or function calls on the $ReadOnlyArray identifier