I am still try to grasp how Flow works, anyone could explain me why this simple example is throwing an error?
function say(text: string) {
console.log(text);
}
say('Hello World!'); // This is alright
const text: ?string = 'Hello World!';
say(text); // Error:(219, 5) Cannot call `say` with `text` bound to `text` because null or undefined [1] is incompatible with string [2].
I know, the text variable can be null, but by the time I call say(text) it is clearly not null.
Flow does not keep track of what you have assigned to what. It only tracks the types of the variables. And you are trying to pass type ?string to string, which isn't a valid assignment since it could be null. You know its not null but flow doesn't because it's not actually executing your code.
It's hard to give you good advice for a workaround because const text: ?string = 'Hello World!'; is a very contrived example, but you can use a refinement to only call say if text has been tested for a non-null value.
const text: ?string = 'Hello World!';
if (text) {
say(text);
}
The only time flow does track what you assign is on variable initialization for implicit typings. But this simply assigns the type of the right hand expression as the type of the variable.
let a: ?string = 'foo'
let b = a; // flow infers the type of b as ?string
Related
I know I'm way off track here but I'm trying to use generics to "preset" particular values of an "Event" type.
What I'm doing is as follows and what I'm expecting is "my-source" to be the console output.
I can see why this code doesn't work, but I'm putting it here to try and illustrate what I want to try and achieve.
type EventDetail = {
shopId: string;
};
export type Event<
TEventDetailType extends EventDetail,
TType extends string,
TSource extends string
> = {
source?: TSource;
body: TEventDetailType;
type?: TType;
};
export type UpdateShopCustomDomainEventDetail = EventDetail & {
domainName: string;
};
export type UpdateShopCustomDomainEvent = Event<
UpdateShopCustomDomainEventDetail,
"update-shop-custom-domain",
"my-source"
>;
const test:UpdateShopCustomDomainEvent = {body:{shopId:"123123", domainName:"asdf"}}
console.log(test.source);
// undefined
Essentially what I am wanting to do is something like the following. Where I send an event, but I define the type of event that I'm sending with a generic. This UpdateShopCustomDomainEvent will force the user to enter a value for shopId and domainName, and it will also set the type value to "update-shop-custom-domain" and the source to "my-source".
putEvent<UpdateShopCustomDomainEvent>({
body: {
shopId:"1234",
domainName:"mydomain.com"
}
});
I'd like to determine if an array type is readonly. This includes ReadonlyArray and readonly prefixed.
Examples:
type a = ReadonlyArray<string>
type b = readonly string[]
The relevant non-exposed TypeChecker code is:
let globalReadonlyArrayType = <GenericType>getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) || globalArrayType;
function isReadonlyArrayType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
}
function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined {
const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined);
return symbol && <GenericType>getTypeOfGlobalSymbol(symbol, arity);
}
function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType {
function getTypeDeclaration(symbol: Symbol): Declaration | undefined {
const declarations = symbol.declarations;
for (const declaration of declarations) {
switch (declaration.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
return declaration;
}
}
}
if (!symbol) {
return arity ? emptyGenericType : emptyObjectType;
}
const type = getDeclaredTypeOfSymbol(symbol);
if (!(type.flags & TypeFlags.Object)) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol));
return arity ? emptyGenericType : emptyObjectType;
}
if (length((<InterfaceType>type).typeParameters) !== arity) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity);
return arity ? emptyGenericType : emptyObjectType;
}
return <ObjectType>type;
}
TypeChecker Method
cspotcode pointed out that you can get IndexInfo via the TypeChecker.
const isReadonlyArrayType = (type: Type) =>
type.checker.isArrayLikeType(type) &&
!!type.checker.getIndexInfoOfType(type, IndexKind.Number)?.isReadonly
TS Compiler Method
The following matches the compiler's logic.
let globalReadonlyArrayType: Type;
export const isReadonlyArrayType = (type: Type): boolean => {
const { checker } = type;
if (!globalReadonlyArrayType) {
const symbol =
checker.resolveName('ReadonlyArray', /* location */ void 0, SymbolFlags.Type, /* excludeGlobals */ false)!;
globalReadonlyArrayType = checker.getDeclaredTypeOfSymbol(symbol);
}
return !!((type as ObjectType).objectFlags & ObjectFlags.Reference) &&
((<TypeReference>type).target === globalReadonlyArrayType);
};
Notes
It appears that there may be no immediate advantage of the TypeChecker method over using the Compiler method. The one concern that I had was that comparing target equality may fail if ReadonlyArray was extended, but it appears that this is currently not possible with TypeScript (v3.9.3)
Logic-wise, if performing isArrayLikeType first, the TypeChecker method would be performing a little more work, but likely not enough to worry about in terms of performance.
With that said, it seems that there may be advantage in the TypeChecker method over the second in the event that TS changes its readonly logic, allows extension of ReadonlyArray, etc.
For that reason, I'd recommend using the TypeChecker method.
If you're not using byots, you could probably replace the call to isArrayLikeType with !!((type as ObjectType).objectFlags & ObjectFlags.Reference)
Caveat: My understanding of ReadonlyArray is at a basic level, as of writing this, so if I'm wrong on any of this, please let me know!
I'm trying to wrap my head around flow and I struggle to make it work with ES6's Map
Consider this simple case (live demo):
// create a new map
const m = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
flow throws:
console.log(m.get('value') * 5)
^ Cannot perform arithmetic operation because undefined [1] is not a number.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
I also tried:
const m:Map<string, number> = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
But I got the same error
I believe this is because flow thinks that the value can also be something else than a number, so I tried to wrap the map with a strict setter and getter (live demo):
type MyMapType = {
set: (key: string, value: number) => MyMapType,
get: (key: string) => number
};
function MyMap() : MyMapType {
const map = new Map();
return {
set (key: string, value: number) {
map.set(key, value);
return this;
},
get (key: string) {
return map.get(key);
}
}
}
const m = MyMap();
m.set('value', 5);
const n = m.get('value');
console.log(n * 2);
but then I got:
get (key: string) {
^ Cannot return object literal because undefined [1] is incompatible
with number [2] in the return value of property `get`.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
get: (key: string) => number ^ [2]
How can I tell flow that I only deal with a Map of numbers?
Edit:
Typescript approach makes more senses to me, it throws on set instead on get.
// TypeScript
const m:Map<string, number> = new Map();
m.set('value', 'no-number'); // << throws on set, not on get
console.log(m.get('value') * 2);
Is there a way to make Flow behave the same way?
What Flow is trying to tell you is that by calling map.get(key), .get(...) may (V) or may not (void) return something out of that map. If the key is not found in the map, then the call to .get(...) will return undefined. To get around this, you need to handle the case where something is returned undefined. Here's a few ways to do it:
(Try)
const m = new Map();
m.set('value', 5);
// Throw if a value is not found
const getOrThrow = (map, key) => {
const val = map.get(key)
if (val == null) {
throw new Error("Uh-oh, key not found")
}
return val
}
// Return a default value if the key is not found
const getOrDefault = (map, key, defaultValue) => {
const val = map.get(key)
return val == null ? defaultValue : val
}
console.log(getOrThrow(m, 'value') * 5)
console.log(getOrDefault(m, 'value', 1) * 5)
The reason that map.get(key) is typed as V | void is the map might not contain a value at that key. If it doesn't have a value at the key, then you'll throw a runtime error. The Flow developers decided they would rather force the developer (you and me) to think about the problem while we're writing the code then find out at runtime.
Random and pretty late, but was searching and came up with this for my own use cases when I didn't see it mentioned:
const specialIdMap = new Map<SpecialId, Set<SpecialId>>();
const set : Set<SpecialId> = specialIdMap.get(uniqueSpecialId) || new Set();
and this saves quite a lot of boilerplate of checking if null and/or whatever. Of course, this only works if you also do not rely on a falsy value. Alternatively, you could use the new ?? operator.
I'm a little bit confuse about the meaning difference of using "?"
I offen saw this:
var foo?: number = "bar"
But also saw this:
function foo(bar: {baz: ?string}) { ... }
And also saw both together.
I've read about invariants and maybe types, but if I understood it right, both signals have the same meaning, which is: "this type is of kind 'X', but it maybe is null or undefined".
Is it right or am I getting it wrong?
Here are answers to most of your questions:
// Don't know what this is, or why you would use it
// Error: undefined is incompatible with string
var foo1?: string = undefined;
// ?string means string, null, or undefined
var foo2: ?string = undefined;
type FooOptional = { foo?: string };
type FooMaybe = { foo: ?string };
// If it's optional it can be completely omitted
var foo3: FooOptional = {};
// It can also be explicitly set to undefined
var foo4: FooOptional = { foo: undefined };
// But not null!
var foo5: FooOptional = { foo: null };
// If it's a maybe type, it must be specified
// Error: property `foo` not found
var foo6: FooMaybe = {};
// But you can set it explicitly to null or undefined
var foo7: FooMaybe = { foo: null };
var foo8: FooMaybe = { foo: undefined };
(tryflow link)
Using both together (e.g. {foo?: ?string} as a type) usually (but not in all cases) indicates that the author doesn't quite know what type they want to use and have just added question marks until it typechecks. Typically I have found that if I think it through, it makes sense to use either an optional property or a maybe type, but not both.
Hi All is there any way to locally define a variable in a function and then pass it to the oher function. I mean to say is it possible the pass a local value from one function to other function.
Somebody Please suggest me the solution.
Thanks in advance
Or it's that simple or you meant something else:
private function function1():void
{
var localVariable:String = "this is local variable of function1()";
function2(localVariable);
}
private function function2(string:String):void
{
trace(string);
}
function1();
or use global variable as temporary storage:
private var globalVariable:String = "";
private function function1():void
{
var localVariable:String = "this is local variable of function1()";
globalVariable = localVariable;
}
private function function2():void
{
trace(globalVariable);
}
function1();
function2();
zdmytriv is right.
Although, you can also make default variables, like so:
(Modifying zdmytriv's code)
private function function1():void
{
var localVariable:String = "this is local variable of function1()";
function2(localVariable);
function2(); //You don't have to enter a default argument
}
private function function2(string:String = "something else"):void
{
trace(string);
}
This would trace:
this is local variable of function1()
something else
A little off topic, but good to know.
Primitives in Flex are passed by value, where complex objects are passed by reference. You can use this to pass objects around without scoping a variable outside the functions themselves. For instance:
private function function1():void {
{
var localVar:Object = {value:"test"};
trace(localVar.value);
function2(localVar);
trace(localVar.value);
}
private function function2(obj:Object):void
{
obj.value = "new value";
}
This would trace:
test
new value
Which reflects the fact that function2 receives the parameter "obj" by reference, as a pointer to the original "localVar" object. When it sets the .value field, that change is reflected in function1.
I just thought I'd point that out.