Turn Undefined Property Access Into an Error - flowtype

I noticed I can define a type for a function and accidentally access properties on that function
type FakeType = {}
type aFunc = string => number
const b: aFunc = () => 1
const a: FakeType = b.whatTheHeck // Flow makes "whatTheHeck" any type
Is there a way to make this an error in Flow?

I don't really know why Flow doesn't error on this automatically, but what you can do is declare aFunc as a callable object, rather than just a function, e.g.
type aFunc = {
(string): number,
};
(On flowtype.com/try)

Related

Dart won't compile because of unmatching types, 'List<T>' can't be assigned to the parameter type 'List<T> Function()'

The relevant part of the code looks like this:
static final Map<String, List<Provider>> _availableProviders = {};
Provider(this.name, this._requiresKey, this._versions) {
_versions.forEach((version) => {
Provider._availableProviders.putIfAbsent(version, <Provider>[]),
Provider._availableProviders[version].add(this)
});
}
_versions is a set of Strings and when I try and run I receive this error:
lib/src/api/Provider.dart:18:71: Error: The argument type 'List<Provider>' can't be assigned to the parameter type 'List<Provider> Function()'.
- 'List' is from 'dart:core'.
- 'Provider' is from 'package:bible/src/api/Provider.dart' ('lib/src/api/Provider.dart').
Provider._availableProviders.putIfAbsent(version, <Provider>[]),
The argument type 'List<Provider>' can't be assigned to the parameter type 'List<Provider> Function()'
I'm not exactly sure if this error has to do with the notation of the empty list I created or with how I instantiated the map. Any help would be appreciated!
Second parameter of Map's putIfAbsent is function, that returns value to be put, not value itself: https://api.dart.dev/stable/2.10.4/dart-core/Map/putIfAbsent.html
Rewrite your code to something like this:
static final Map<String, List<Provider>> _availableProviders = {};
Provider(this.name, this._requiresKey, this._versions) {
_versions.forEach((version) => {
Provider._availableProviders.putIfAbsent(version, () => <Provider>[]),
Provider._availableProviders[version].add(this)
});
}

How to create a Flow Union runtime refinement without embedding literals

Hello kind Stackoverflow folks,
I'm trying to create a function to guard off code from being executed at run-time with an incorrect Flow type present.
My understanding is that the way to do this at run-time is by refining, or checking, that the type matches what is required and using Flow to keep an eye that no cases are missed along the way.
A simple case is where I have a string input that I would like to confirm matches to a enum/Union type. I have this working as I would expect with literals e.g.
/* #flow */
type typeFooOrBaa = "foo"| "baa"
const catchType = (toCheck: string): void => {
// Working check
if (toCheck === "foo" || toCheck === "baa") {
// No Flow errors
const checkedValue: typeFooOrBaa = toCheck
// ... do something with the checkedValue
}
};
Try it over here
Naturally, I would like to avoid embedding literals.
One of the things I've tried is the equivalent object key test, which doesn't work :-( e.g.
/* #flow */
type typeFooOrBaa = "foo"| "baa"
const fooOrBaaObj = {"foo": 1, "baa": 2}
const catchType = (toCheck: string): void => {
// Non working check
if (fooOrBaaObj[toCheck]) {
/*
The next assignment generates the following Flow error
Cannot assign `toCheck` to `checkedVariable` because: Either string [1] is incompatible
with string literal `foo` [2]. Or string [1] is incompatible with string literal `baa` [3].",
"type"
*/
const checkedVariable: typeFooOrBaa = toCheck
}
};
Try it over here
Is it possible to achieve something like this without having to go down the full flow-runtime route? If so how is it best done?
Thanks for your help.
One approach that appears to works is to use the const object which defines the allowed values, to:
Generate a union type using the $keys utility.
Use that union type to create a map object where the keys are the desired input (our case strings) and the values are "maybe"s of the type that needs refining.
Here's the example from earlier reworked so that it:
Sets the type up as we'd expect to allow either "foo" or "baa" but nothing else.
Detects when a string is suitably refined so that it only contains "foo" or "baa".
Detects when a string might contain something else other than what's expected.
Credit to #vkurchatkin for his answer that helped me crack this (finally).
/* #flow */
// Example of how to persuade Flow to detect safe adequately refined usage of a Union type
// at runtime and its unsafe, inadequately refined counterparts.
const fooOrBaaObj = {foo: 'foo', baa: 'baa'}
type typeFooOrBaa = $Keys<typeof fooOrBaaObj>
// NB: $Keys used inorder for the type definition to avoid aliasing typeFooOrBaa === string
// which allows things like below to correctly spot problems.
//const testFlowSpotsBadDefition: typeFooOrBaa = "make_flow_barf"
const fooOrBaaMap: { [key: string]: ?typeFooOrBaa } = fooOrBaaObj;
// NB: Use of the "?" maybe signifier in the definition a essential to inform Flow that indexing into
// the map "might" produce a "null". Without it the subsequent correct detection of unsafe
// unrefined variables fails.
const catchType = (toCheck: string): void => {
const myValue = fooOrBaaMap[toCheck];
if (myValue) {
// Detects refined safe usage
const checkedVariable: typeFooOrBaa = myValue
}
// Uncommenting the following line correctly causes Flow to flag the unsafe type. Must have the
// "?" in the map defininiton to get Flow to spot this.
//const testFlowSpotsUnrefinedUsage: typeFooOrBaa = myValue
}
Have a play with it over here
You can type the object as {[fooOrBaa]: number}, but flow will not enforce that all members of fooOrBaa exist in the object.

Encaps string in flow

I created this flow demo - https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVBjOA7AzgFzAEEwBeMACgEMN8BLHAFQE8AHAUwC4wCAnO7AHMAlGQB8YAOQBhAPIBVAHKMAogCUA+pLABqMDXpM27MMGDFaDbCw4AFXuyh0AHu17oseQgCEYVABZkxAAGAEZ+-sEA3EA
I am using React Native and Flow.
My code is:
const A = (actionType: string) => 'COUNTER_' + actionType // ActionTypePrefixer
const UP = A`UP`;
My goal is to call a function with backticks.Flow highlights this as an error saying:
Is there any way to do encaps with:
[flow] array (This type is incompatible with the expected param
type of string See also: encaps tag)
Screenshot:
Flow highlights this as an error
That's because it is an error! The first parameter to a template literal function is an Array<string>, not a string. Your code would still execute because ['foo'].toString() === 'foo', but it would easily break in the general case. Therefor you should change
const A = (actionType: string) => 'COUNTER_' + actionType;
to
const A = (actionType: Array<string>) => 'COUNTER_' + actionType[0];
Potentially it might also be good to throw an exception if actionType.length > 1.

How to declare a flowtype library definition for polymorphic functions

What is the proper way to specify the type definitions a polymorphic method that depending on the parameter types has different return types?
index.js:
// #flow
import {func1} from './lib1';
const s: string = func1('string');
const b: boolean = func1(); // should cause type error but does not!
lib1.js:
export function func1(p) {
return (typeof p === 'string') ? p : 0;
}
defs/lib1.js.flow
// #flow
declare module "lib1" {
declare export function func1(p: string): string;
declare export function func1(_: void): number;
}
.flowconfig:
[libs]
defs/
I would have hoped to received an error message in index.js(4) but flow does not complain!
Yes, the example you gave is how to declare an overloaded function. However, you may want to change the second line to:
declare function myFunc(_: void): number;
Since Flow allows a function to be called with too many arguments (though not for much longer), it may select the second overload even if the function is called with a string. The modification I suggest makes it so the argument must be undefined (which is what is implicitly passed if you just leave off an argument).

exported typed functions that return functions should NOT require the returned function to be typed to work in other modules

For example if you are using redux-actions and have created the following module definition for it:
declare module 'redux-actions' {
declare type ActionType = string
declare type Action = {
type: ActionType,
payload?: any,
error?: bool,
meta?: any,
}
declare function createAction<T>(
type: string,
payloadCreator?: (...args: Array<T>) => any,
metaCreator?: Function
): (...args: Array<T>) => Action
}
and then you use that function to return a new function like this:
export const selectProfileTab = createAction('SELECT_PROFILE_TAB', (index: number) => {
playSound()
return { index }
})
and then in another file use it incorrectly like this:
selectorProfileTab('someString')
that won't report an error. It seems to be because flow requires annotations at the "boundaries" of modules. Am I correct?
Because the following does work:
export const selectProfileTab: (index: number) => any = createAction('SELECT_PROFILE_TAB', (index: number) => {
playSound()
return { index }
})
Notice I've annotated the returned function. The following will now produce an error:
selectProfileTab('someString')
I'm just trying to get a hold of this and verify this because it's a lot of extra "boilerplate" to annotate those returned functions, especially when it calling selectProfileTab('someString') would correctly produce an error if used in the same file. It makes you think: what's the point in creating module definitions for the redux-actions package which doesn't have them yet if it doesn't provide any/much value since you have to annotate your returned functions anyway. That's quite disappointing. Am I correct in determining that it's a module "boundaries" limitation/requirement with Flow? Are there any ways to get the desired result of not having to type returned functions that you export?

Resources