Why does Flowtype allow assigning empty object ({}) to a type? - flowtype

Can someone explain to me why a2 doesn't give any error?
type A = {
name: string;
type: number;
};
const a1: A = null; // NOT OK
const a2: A = {}; // OK
const a3: A = { name: "aaa" }; // NOT OK
const a4: A = { name: "aaa", type: 6 }; // OK
I tried the same thing in Typescript and it a2 doesn't compile. And I agree with Typescript. Why does Flowtype think it's ok? Is there a setting which would allow me to make it not ok?

{} seems to be a special thing - unsealed object (https://flow.org/en/docs/types/objects/#toc-unsealed-objects). The idea is to allow you to fill the object step by step:
type A = {
name: string;
type: number;
};
const a2: A = {};
a2.name = "FOO";
a2.type = 3;
There seems to be a lot of confusion about how it should work exactly in various different situations:
https://github.com/facebook/flow/issues/2327
https://github.com/facebook/flow/issues/2977

Related

VUE 3 TS2345 when pushing an Array into another Array

as the summary mentions, i want to push a new array(Category) into another array(categoryLists)
in an exercise using vue 3 composition api, though i found something that works, i wish to try out using composables into my code
this is the declaration
const categoryLists = ref([{
id: Date.now(),
label: 'Default',
}]);
found this to be working
function addNewCategory() {
if (newCategoryName.value === '') {
console.log('no title, not added');
} else {
categoryLists.value.push({
id: Date.now(),
label: newCategoryName.value,
});
console.log(categoryLists.value);
newCategoryName.value = '';
}
}
but when i tried to use composables for this function, i instead get an error ts2345 saying that the below is un assignable to categoryLists
categoryLists.value.push(NewCategory(Date.now(), newCategoryName.value));
console.log(categoryLists);
newCategoryName.value = '';
below is the composable .ts file code
export default function NewCategory(
identifier: number,
value: string,
) {
// const Category = [identifier, value];
const Category = ref([{
id: identifier,
label: value,
}]);
console.log(Category);
return Category;
}
originally, my values are using refs, so i tried to change them to reactives but i still face the same issue
also tried not having Category as a ref
const Category = [identifier, value];
but this still shows the same issue
Argument of type 'Ref<{ id: number; label: string; }[]>' is not assignable to parameter of type '{ id: number; label: string; }'.
Type 'Ref<{ id: number; label: string; }[]>' is missing the following properties from type '{ id: number; label: string; }': id, label
can anyone show a solution or possibly explain why this isnt working

flow returns wrong type

I have this piece of code:
/* #flow */
import { List } from 'immutable';
type NotMapped = {|
first: Array<number>,
second: number,
|};
type Mapped = {|
first: List<number>,
second: number,
|};
const notMapped: NotMapped = {
first: [1, 2, 3],
second: 10,
};
const map = () => {
const first = List(notMapped.first);
return { ...notMapped, first };
}
const result: Mapped = map();
I want result to beMapped type but I get:
{|
first: List<number> | Array<number>,
second: number
|}
How come that flow thinks it might be Array<number> when I explicitly set first as List(notMapped.first)? It only works if I set return { second: notMapped.second, first };, but this isn't a solution because I have large amount of data and I cannot set every item.
I have checked and this is well-known issue, but when spreading types, not object with assigned type. Is there any solution for this?

Flowtype - generic array

How i can write generic function, which take Array of Objects (any type of Object, possible even null and undefined), and filter it to return just valid items of array? If i write it lite this, i will lose genericity :/
// #flow
// Types
type Person = {
id: string,
name: string,
};
type Car = {
id: string,
color: string,
};
// Function definition
const isNotUndefinedOrNull = item => !(item === null || item === undefined);
export const trimList = (list: Array<any> | $ReadOnlyArray<any>): Array<any> => {
return list.filter(isNotUndefinedOrNull);
};
// Constants
const persons = [{ id: 'p1', name: 'Johny' }, null, undefined];
const cars = [{ id: 'c1', color: 'red' }, null, undefined];
// Calls
const trimmedPersons = trimList(persons);
const trimmedCars = trimList(cars);
PROBLEM is, there i have trimmed cars and persons, but flow doesnt know, there is Cars in the trimmedCars list and neither know there is Persons in trimmedPersons list. Flow see just Array and i dont know, how to write is right, to not lose this info.
Flow try
As flow has a bug with Refine array types using filter we use explicit type casting ((res): any): T[]).
function filterNullable<T>(items: (?T)[]): T[] {
const res = items.filter(item => !(item === null || item === undefined);
return ((res): any): T[]);
}
// Example
const a: number[] = filterNullable([1, 2, null, undefined]);
i found it :)
export function trimList<V>(list: Array<?V> | $ReadOnlyArray<?V>): Array<V> {
return R.filter(isNotUndefinedOrNull, list);
}

Function with union argument indirectly causes inexplicable errors on union members

In the following block of code, flow errors occur on the OuterY and OuterZ type definitions only when the getInnerValues function is present.
The errors complain that "Y" and "Z" are incompatible with "X". For example: "string literal Y is incompatible with string literal X.".
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer = OuterX | OuterY | OuterZ;
// If the next line is present, errors occur on
// lines 6 and 7 complaining that "Y" and "Z" are
// incompatible with "X". When the next line is
// commented out, the errors go away. Why??
const getInnerValues = (outer: Outer) => outer.inner.map(inner => inner.value);
Why is this happening?
Click here to see the issue on flow.org/try
Click here to see the same issue with stricter typing on flow.org/try
Flow doesn't realize that there exists an inner property of type {value: string} for all the possible cases of Outer. One way to fix this is to type the function to accept an object with the expected type:
(Try)
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer = OuterX | OuterY | OuterZ;
// no errors
const getInnerValues = (outer: {inner: Array<{value: string}>}) =>
outer.inner.map(inner => inner.value);
Another way to do this (probably the better way) is to redefine Outer as type which accepts a type parameter. Then you can generically type your getInnerValues function to accept generic Outer instances:
(Try)
/* #flow */
type Inner<T> = { value: T };
type OuterX = { inner: Array<Inner<"X">> };
type OuterY = { inner: Array<Inner<"Y">> };
type OuterZ = { inner: Array<Inner<"Z">> };
type Outer<T> = {
inner: Array<Inner<T>>
}
// no errors
const getInnerValues = <T>(outer: Outer<T>) => outer.inner.map(inner => inner.value);

Flowtype generic function with different parameters

I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)

Resources