Are { [string]: string } and { [string]: (string | number} } incompatible is right? - flowtype

string and string | number are compatible, but { [string]: string } and { [string]: (string | number} } are incompatible.
Am i doing something wrong?
https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgAqAjGALxgDOGATgJYB2A5gNya4GEBM5VtjTMAB8wDAK4BbAEZ4abbPiIBmXgG8wAbWr1mAXQBcfHYIC+8jkQAsazdoEGiPE+ihiGAYwx04DMBICGjAAUAJSqqGBgAG7+NNHEhiS8AORQcHDJbJExcVFciTwUUcRsEdGx0UqJKhTqUrGGyfU0yWBmZTnRlonWRUpszkA
/* #flow */
type T1 = string;
type T2 = string | number;
type T3 = { [string]: string };
type T4 = { [string]: T2 }
function main(){
var v1: T1 = 'foo';
var v2: T2 = v1;
var v3: T3 = { bar: 'bar' };
var v4: T4 = v3;
}
13: var v4: T4 = v3;
^ Cannot assign `v3` to `v4` because string [1] is incompatible with number [2] in the indexer property. [incompatible-type]
References:
5: type T3 = { [string]: string };
^ [1]
6: type T4 = { [string]: T2 }
^ [2]

Reason
This is normal and expected
If this was allowed, then you would be allowed to do
v4.foo = 1;
var expectAString : string = v3.foo;
And you would get a number even though due to v3's type you should have only got a string.
Solution
Mark the objects as read-only:
type T3 = $ReadOnly<{ [string]: string }>;
type T4 = $ReadOnly<{ [string]: T2 }>;
Flow try link
Since they are read-only this prevents you to actually do v4.foo = 1;.
Note
This is the same for array, see this answer.
Docs
$ReadOnlyArray’s type parameter is covariant while Array’s type parameter is invariant
Invariance, covariance

Related

Get value of optional types when retrieving value of record fields in F#

I have a type defined as follows:
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
and an instance of this type:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
I want to extract the field names and values from this record type using reflection like the following:
let getFieldValueMappingOfARecordType (data: 'T) : seq<string * obj> =
let fieldValueMapping =
data.GetType()
|> FSharpType.GetRecordFields
|> Seq.map (
fun propertyInfo ->
(propertyInfo.Name, data |> propertyInfo.GetValue)
)
fieldValueMapping
Then invoking the above function with the instance of employee type
let mapping = getFieldValueMappingOfARecordType emp
|> Seq.toList
gives us:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", Some "E")]
So far it's working well with non-optional type. But in case of optional types, it's returning the value of the field as either Some value or None. What I would like to do is to get the value when the field has Some value or make it null when it's None.
Essentially like the follwing:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", "E")]
Or if the employee instance is like the following:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = None
}
Then,
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", null)]
This is what I have so far (non-working code):
open System
open Microsoft.FSharp.Reflection
open System.Reflection
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
let getSomeOrNull (t: Type) (o: obj) =
let opt = typedefof<option<_>>.MakeGenericType [| t |]
match (o :?> opt) with
| Some s ->
s
| None ->
null
let getValues (data: 'T) =
let values =
data.GetType()
|> FSharpType.GetRecordFields
|> Array.map (
fun propertyInfo ->
let value =
data |> propertyInfo.GetValue
let isOption =
propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>
match isOption with
| true ->
(propertyInfo.Name, (getSomeOrNull propertyInfo.PropertyType value))
| false ->
(propertyInfo.Name, value)
)
values
getValues emp
|> printfn "%A"
I think the only way to do this is with reflection:
let getSomeOrNull (t: Type) (o: obj) =
if isNull o then null
else t.GetProperty("Value").GetValue(o)
I think this should do the trick:
let getSomeOrNull (o: obj) =
match o with
| :? Option<string> as o -> a |> Option.toObj > box
| _ -> null

Flow equivalent to Java Wildcard?

Is there an equivalent to Java's Wildcards in Flow?
Here's my example code I've been working on as a test:
type InterfaceType = {
var1 : number,
};
type ActualType = InterfaceType & {
var2 : string,
};
type InterfaceGenericType<T : InterfaceType> = {
var3 : T,
}
type ActualGenericType = InterfaceGenericType<ActualType> & {
}
class State<T : InterfaceGenericType<InterfaceType>> {
prop : T;
constructor(arg : T) : State<T> {
this.prop = arg;
return this;
}
}
let actual : ActualType = {
var1: 1,
var2: "two",
};
let actualGeneric : ActualGenericType = {
var3 : actual,
}
let s2 = new State(actualGeneric);
This is the flow error I'm getting:
40: let s2 = new State(actualGeneric);
^ Cannot call `State` with `actualGeneric` bound to `arg` because property `var2` is missing in `InterfaceType` [1] but exists in object type [2] in property `var3`.
References:
20: class State<T : InterfaceGenericType<InterfaceType>> {
^ [1]
7: type ActualType = InterfaceType & {
^ [2]
I know I can get around the issue by doing:
class State<I : InterfaceType, T : InterfaceGenericType<I>> {
but I'm trying to not have to declare both types.
We can trim down your code a bit to remove the class:
type InterfaceType = { var1: number };
type ActualType = InterfaceType & { var2: string, };
type InterfaceGenericType<T : InterfaceType> = {
var3: T,
};
let actual: ActualType = {
var1: 1,
var2: "two",
};
let actualGeneric: InterfaceGenericType<ActualType> = {
var3: actual,
};
let v: InterfaceGenericType<InterfaceType> = actualGeneric;
I can't speak to Java since I don't know it, but I can tell you how to fix this. If we look at the error for this code:
17: let v: InterfaceGenericType<InterfaceType> = actualGeneric;
^ Cannot assign `actualGeneric` to `v` because property `var2` is missing in `InterfaceType` [1] but exists in object type [2] in type argument `T` [3].
References:
17: let v: InterfaceGenericType<InterfaceType> = actualGeneric;
^ [1]
2: type ActualType = InterfaceType & { var2: string, };
^ [2]
4: type InterfaceGenericType<T : InterfaceType> = {
^ [3]
The core issue is that v's type InterfaceGenericType<InterfaceType> would for example allow you to do
v.var3 = { var1: 42 };
because that is a valid InterfaceType object. That isn't a valid ActualType object, but by assigning actualGeneric to v, you've essentially erased that type information, which means that if your code were allowed as-is, the assignment would corrupt your actualGeneric object's type.
The fix for this is to tell Flow that the var3 property is read-only, by changing
var3: T,
to be
+var3: T,

How to extend a union type with an additional property?

I'm using disjoint union types to represent events, as is recommended for Redux-like actions. Generally this is working well, but in some parts of my app, events have an additional timestamp field. How do I annotate the type of a timestamped event without duplicating something?
I tried using intersection types to merge the additional required property, but the following fails:
/* #flow */
export type EvtA = {type: 'A', prop1: string};
export type EvtB = {type: 'B', prop2: string};
export type Event =
| EvtA
| EvtB;
type Timestamped = { timestamp: number };
type TSEvent = Event & Timestamped;
function show(event : TSEvent) {
console.log(event.timestamp);
// let event = ((e: any): Event);
if (event.type === 'A') {
console.log(event.prop1);
}
}
Error (on http://flow.org/try):
function show(event : TSEvent) {
^ all branches are incompatible: Either property `prop1` is missing in `EvtB` [1] but exists in `EvtA` [2]. Or property `prop1` is missing in `Timestamped` [3] but exists in `EvtA` [2]. Or property `prop2` is missing in `EvtA` [1] but exists in `EvtB` [4]. Or property `prop2` is missing in `Timestamped` [3] but exists in `EvtB` [4].
References:
12: type TSEvent = Event & Timestamped;
^ [1]
7: | EvtA ^ [2]
12: type TSEvent = Event & Timestamped;
^ [3]
8: | EvtB;
^ [4]
17: console.log(event.prop1);
^ Cannot get `event.prop1` because: Either property `prop1` is missing in `EvtB` [1]. Or property `prop1` is missing in `Timestamped` [2].
References:
12: type TSEvent = Event & Timestamped;
^ [1]
12: type TSEvent = Event & Timestamped;
^ [2]
The commented-out typecast is my current hacky workaround.
(Yes, perhaps the cleaner approach would have been type LogEntry = { event: Event, timestamp: number }, but that requires changing a lot of other code.)
What you're probably looking for is an object spread:
(Try)
/* #flow */
export type EvtA = {type: 'A', prop1: string};
export type EvtB = {type: 'B', prop2: string};
export type Event =
| EvtA
| EvtB;
type Timestamped = {timestamp: number };
type TSEventA = {
...EvtA,
...Timestamped,
};
// TSEventA now has type:
// {prop1?: mixed, timestamp?: mixed, type?: mixed}
function show(event : TSEventA) {
console.log(event.timestamp);
// let event = ((e: any): Event);
if (event.type === 'A') {
console.log(event.prop1);
}
}
You can retain all the type information by spreading exact objects:
(Try)
/* #flow */
export type EvtA = {|type: 'A', prop1: string|};
export type EvtB = {|type: 'B', prop2: string|};
export type Event =
| EvtA
| EvtB;
type Timestamped = {|timestamp: number|};
type TSEvent = {
...Event,
...Timestamped
};
// TSEvent now has union type:
// {prop2: string, timestamp: number, type: "B"}
// | {prop1: string, timestamp: number, type: "A"}
function show(event : TSEvent) {
console.log(event.timestamp);
if (event.type === 'A') {
console.log('A', event.prop1);
} else if (event.type === 'B') {
console.log('B', event.prop2);
}
}

Flow-js: How to create a variable consistent with an intersection of array and object

I have this valid flow-js definition for mysql :
declare type QueryResults = Array<Object> &{
insertId?: string | number,
affectedRows?: number,
changedRows?: number
};
And I've trying to create a variable that would be consistent with this definition. (You can try it here):
/* #flow */
type A = Array<Object>
type B = {
insertId?: string | number,
affectedRows?: number,
changedRows?: number
}
type C = A & B;
let a1: A = []
let a2: A = [{}]
let b1: B = {}
let b2: B = {insertId: 3}
let c: C
c = [] // Error, not complient with B
c.insertId = 5 // Error, not complient with A
Well, I just found out see here
c = {}.extend([])

Is it possible to create a vector with references to lazy-static values?

The following code compiles:
let x = Regex::new(r"\d+").unwrap();
let y = Regex::new(r"asdf\d+").unwrap();
let regexes = vec![x, y];
But this code does not:
lazy_static! {
static ref X_PRIME: Regex = Regex::new(r"\d+").unwrap();
static ref Y_PRIME: Regex = Regex::new(r"asdf\d+").unwrap();
}
let regexes = vec![X_PRIME, Y_PRIME];
The error is:
error[E0308]: mismatched types
--> src\syntax\lex.rs:19:33
|
19 | let regexes = vec![X_PRIME, Y_PRIME];
| ^^^^^^^ expected struct `syntax::lex::lex::X_PRIME`, found struct `syntax::lex::lex::Y_PRIME`
|
= note: expected type `syntax::lex::lex::X_PRIME`
= note: found type `syntax::lex::lex::Y_PRIME`
Yes. lazy_static gives X_PRIME and Y_PRIME distinct types, but they both implement Deref<Regex>, so you could write:
let regexes = vec![&*X_PRIME, &*Y_PRIME];
// The * dereferences the values to a `Regex` type
// The & turn them back into references `&Regex`.
You could also just define another static:
lazy_static! {
static ref X_PRIME: Regex = Regex::new(r"\d+").unwrap();
static ref Y_PRIME: Regex = Regex::new(r"asdf\d+").unwrap();
static ref REGEXES: Vec<&'static Regex> = vec![&X_PRIME, &Y_PRIME];
}

Resources