Flow - maybe type incompatible with union types - javascript

Flow code can be run here.
Using flow, I have a function that takes a key value pair object and gets a value for it - the value it gets should be a string, number or boolean.
type ValueType = string | number | bool | null | void;
type ObjectOfValues = {[string]: ValueType}
function getValueFromObjectOfValues(objectOfValues: ObjectOfValues, name: string): ValueType {
return objectOfValues[name];
}
I define some object type that has a property that's a maybe string:
type SomeValueWithNullableString = {
someProperty: ?string
}
Then I create a function that takes my specific object type and calls the function to get a value from it:
function getValue (someObject: SomeValueWithNullableString) {
return getValueFromObjectOfValues(someObject, 'someProperty');
}
This results in a flow error:
type ObjectOfValues = {[string]: ValueType}
^ boolean. This type is incompatible with the expected param type of someProperty:
?string ^ string 2: type ObjectOfValues =
{[string]: ValueType}
^ number. This type is incompatible with the expected param type of 9: someProperty:
?string ^ string
What am I doing wrong?

The problem with this code is that the objects are mutable, so getValueFromObjectOfValues could legally do objectOfValues.someProperty = 5.
If Flow allowed this subtyping relationship, then the original caller, who thought they had an object where someProperty had type ?string, would now have an object where someProperty had type number, thereby breaking the type system.
To solve this problem, you can use property variance. You need to change your type like this:
type ObjectOfValues = {+[string]: ValueType}
This means, loosely, that if you have an object of type ObjectOfValues, all you know is that its properties are some subtype of ValueType. This means that when you read from them, you will get a ValueType. But Flow won't let you write to them, since it doesn't know what type they actually are -- just that they are a subtype of ValueType.

Related

How to deduce the type of function argument by value of previous argument?

Using typescript, I want the compiler/IDE to deduce the type of argument when it can. How do I make it work ?
First argument of my function is a string and it's value will decide the type of data that can be passed as the second argument. But I am not able to do that. I am sharing how I expected the compiler to work in this case but it is not helping me.
interface AuxType {
name: string,
user: number
}
type ValueType = AuxType[keyof AuxType]
function run(key: string, value: ValueType) {
return dostuff(key, value)
}
run('name', "Stack") // Works as expected
run('user', 2) // Works as expected
run('name', 2) // Expecting this to error but it is not
run('user', "Stack") // Expect this to error but it works in typescript
Is it even possible in typescript ? Shouldn't this be possible with string literal value of the first argument ?
You need to use a generic. Right now, you are just defining ValueType as the union of the types of name and user, i.e. string | number, but it has no dependency on which key is actually passed to the function. To make the key and value depend you need to use a generic with your function, like this:
function run<T extends keyof AuxType>(key: T, value: AuxType[T]) {
return dostuff(key, value)
}
Now the key must be a keyof AuxType (thats what the extends is doing) and the value must be the corresponding type of that key in AuxType.

How to type plain object in TypeScript

I wanna type a plain object of JavaScript, and write this:
interface PlainObject {
[k: string]: any;
}
But I found:
type A = keyof PlainObject // string | number
interface PlainArray {
[k: number]: any;
}
type B = keyof PlainArray // number
Question 1: Why this happens?
Question 2: How can I type a plain object like { name: 'timi' }
This behavior is by design, the string index will mean the object is indexable by a string or a number. The docs say it best:
If you have a type with a string index signature, keyof T will be string | number (and not just string, since in JavaScript you can access an object property either by using strings (object['42']) or numbers (object[42])). And T[string] is just the type of the index signature.
There is no way to have a type for which keyof will return just string. (There is the --keyofStringsOnly compiler option but that is there mostly for backward compatibility and should not be enabled on new code)

Unrefinement casting in flow, with nested types in objects (and arrays of said objects)

Well consider the following snippet, where I have two object types. With the second type being a more refined version of the first type:
/* #flow */
type A = {|
value: string | number,
|}
type B = {|
value: number,
|}
const b:B = { value: 2 };
const a:A = { value: 2 }; //works.
const z:A = b; //fails, but for javascript it is exactly the same as line above
This fails on the last line, since flow tells me that "number" is incompatile with "string". However the type is "union" not intersection, so it should work? (Just to use the type again I'd have to refine it again).
In my real code I'd actually have arrays of said objects, and objects contain a lot more data, so manually copying the object isn't the way to go.
I realize this is an abstract overview, but in the end I wish to have a function that takes a "refined A" and returns back "A", ie a more real example:
type A = {|
value: string | number,
value2: string,
|}
function foo(input: $ReadOnlyArray<{| ...$Exact<A>, value: number|}>): Array<A> {
return input.filter(v => v.value === 2);
}
Is there a better way to say "this function takes a refined version of type X".
The last paragraph in the Depth Subtyping page of the docs sort of explains what's happening here (the docs admittedly kinda suck).
By default, object properties are invariant, which allow both reads and writes, but are more restrictive in the values they accept.
Right now the value property on A is invariant by default. This means that it only accepts its given type, very specifically. You can't assign it something that is number or string, you can only assign it something that is number | string. Anything that has a given type that is more or less explicit than number | string won't be accepted:
type Ambiguous = {|
value: string | number,
|};
type Specific = {|
value: number,
|};
const myAmbiguous: Ambiguous = ({ value: 1 }: Specific); // error!
One thing we can do about this is mark the property as covariant, as mentioned earlier in the same paragraph:
The plus sign indicates that the [...] property is “covariant.” Using a covariant property allows us to use objects which have subtype-compatible values for that property.
This should allow us to assign a more specific type (in this case number) to our property:
type Ambiguous = {|
+value: string | number,
|};
type Specific = {|
value: number,
|};
const myAmbiguous: Ambiguous = ({ value: 1 }: Specific); // all good
Note, however, that value is now read-only:
myAmbiguous.value = 3; // error!
// ^ Cannot assign `3` to `myAmbiguous.value`
// because property `value` is not writable.
I would recommend reading through the rest of that page on Depth Subtyping and also the page on Type Variance.
Those are 2 different types and flow doesn’t allow the assignment.
Your 2 lines aren’t the same
- the first one creates an object of type A and assigns it
- the second wants to assign an object of type B to a variable of type A

Why does Typescript have Capitalized and lowercase types? [duplicate]

I meant to write a parameter of type number, but I misspelled the type, writing Number instead.
On my IDE (JetBrains WebStorm) the type Number is written with the same color that is used for the primitive type number, while if I write a name of a class (known or unknown) it uses a different color, so I guess that somehow it recognizes the misspelled type as a correct/almost-correct/sort-of-correct type.
When I compile the code, instead of complaining for example that the compiler couldn't found a class named Number, TSC writes this error message:
Illegal property access
Does that mean that number and Number both co-exists as different types?
If this is true, which is the difference between those classes?
If this is not the case, then why it simply didn't write the same error message it displays for unknown classes ("The name 'Number' does not exist in the current scope")
This is the code:
class Test
{
private myArray:string[] = ["Jack", "Jill", "John", "Joe", "Jeff"];
// THIS WORKS
public getValue(index:number):string
{
return this.myArray[index];
}
// THIS DOESN'T WORK: ILLEGAL PROPERTY ACCESS
public getAnotherValue(index:Number):string
{
return this.myArray[index];
}
}
To augment Ryan's answer with guidance from the TypeScript Do's and Don'ts:
Don't ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are
almost never used appropriately in JavaScript code.
/* WRONG */
function reverse(s: String): String;
Do use the types number, string, boolean, and symbol.
/* OK */
function reverse(s: string): string;
JavaScript has the notion of primitive types (number, string, etc) and object types (Number, String, etc, which are manifest at runtime). TypeScript types number and Number refer to them, respectively. JavaScript will usually coerce an object type to its primitive equivalent, or vice versa:
var x = new Number(34);
> undefined
x
> Number {}
x + 1
> 35
The TypeScript type system rules deal with this (spec section 3.7) like this:
For purposes of determining subtype, supertype, and assignment
compatibility relationships, the Number, Boolean, and String primitive
types are treated as object types with the same properties as the
‘Number’, ‘Boolean’, and ‘String’ interfaces respectively.
As the TypeScript doc says:
var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers
are 64-bit floating-point numbers.
As it says, take any as parameter and return number or null
It give an easy way to check a value is number or not
Number("1234"); // 1234
Number("1234.54") // 1234.54
Number("-1234.54") // -1234.54
Number("1234.54.33") // null
Number("any-non-numeric") // null
So simply we can use to check the number, like:
if(Number(val)){
console.log('val is a number');
} else {
console.log('Not a number');
}

Flowtype: Subtype relationship with object with optional properties

What is the typing relationship between type A & type B?
type A = {
a: string,
}
type B = {
a: string,
b?: string, // Same thing as —> "b: void | string"
}
// "-" means contravariant & write only & can accept supertypes
function test<-T: B>(value: T): void {}
declare var foo: A
test(foo)
Flowtype.org (Try Flow)
https://flowtype.org/try/#0PTAEAEDMBsHsHcBQiAuBPADgU1AQVALygDeiooAhgFygDOKATgJYB2A5gDSIC+y62oAEKESZSjXrN2XcgCMA-BMatOoEKADKFALY4UACxWVaoQCgEAPlAAiWTQBusJgBNQAHzrL2VnsnVWAtFaguhQsJgDGsCyMFHYUzKEooABkoPDMKDhR0GgpoOGhlOHhWBhJtACu2Az8WLSIkBUs4ShMUaCZ9AA8-gAqNILmABRx0BVYNL0AlPaOLsS8iE5Y4dDxOHEMoJCwsDS4fHUoQzuwU0A
According to https://flowtype.org/docs/variance.html#object-types....
...the subtyping relationship between objects is derived from the
subtyping relationships of their properties.
Since b?: string is the same thing as b: void | string, does that mean we are comparing two distinct value types (with different properties) with no relationship?
It seems logical to me that { a: string } would be a "supertype" of { a: string, b: string }, since it is a more specific "value type." ...the same thing would go for { a: string } and { a: string, b?: string }.
What is the typing relationship between type A & type B?
B is a subtype of A.
You can verify this by typchecking code like this:
let b: B = {a:'bar', b:'wibble' };
let a: A = b;
// "-" means contravariant & write only & can accept supertypes
function test<-T: B>(value: T): void {}
I think this might be where you are getting confused.
You can't read from a contravariant value, which means it doesn't make very much sense as the argument to a function. And this isn't just an arbitrary restriction: if you could use the value, you wouldn't be able to do anything with it because you couldn't make any assumptions about what it is.
When we talk about covariant types, we are usually talking about function arguments. The function which uses that argument can usually just ignore any specialization and therefore deal with any subtype. We specify the variance from the perspective of a value consumer, and we're expressing which providers are compatible.
A contravariant type is usually a return value. We specify a contravariant return type from the perspective of a provider. The supertype restriction comes in by saying that any consumer of this value must not expect something more specific that we're giving it. For example, specifying a contravariant return type of Dog means that I'm going to give you some kind of Dog, but you may not assume anything more than that: you can treat it like a Mammal or a HasFur, but you can't treat it like a Poodle or else you're going to have problems, which is what the typechecker prevents here.

Categories

Resources