Comments (67)
Very close to done, hold tight for a few more weeks! :)
On Tuesday, August 30, 2016, Fernando Montoya [email protected]
wrote:
What is the state of this bug? :)
โ
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
#34 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AFFNMYQ11Fw7KO6efLJjdIXiJLTEw8LRks5qlIFGgaJpZM4C9bjY
.
from flow.
Agree, %checks
is half working solution.
from flow.
What's the status of this?
from flow.
Any traction on this issue?
from flow.
Isn't this conversation deviating quite far from the purpose of this issue?
Custom predicate types would have lots of wonderful use cases. The jsx control statements -plugin probably won't be one of them, for now at least.
from flow.
Would be nice to see this prioritised.
from flow.
Just to continue documenting TypeScript's progress on this front, the recent release (3.7) includes an extension to the syntax they added in 2015 to include throwing functions like assertIsString()
which is really similar to @gabelevi's suggested "explodes" idea in one of the first comments in this issue.
I'd still love to see both features in Flow!
from flow.
Certainly we could hardcode this stuff in the typechecker, but I would love to come up with syntax to declare this kind of knowledge. We performed this kind of thought experiment with Hack without much success.
So for an isFunction(x)
test, it would be great if we could declare that, if isFunction(x)
returns true
, then x is a function. Otherwise it is not a function. So maybe something like
declare function isFunction<T>(T x): true when T = function, false otherwise;
But that's kind of verbose. Maybe overloading can help?
declare function isFunction(x: function): true;
declare function isFunction(x: not function): false;
Ideally you'd also be able to declare when certain functions will throw, like in an assertFunction(x)
function
declare function assertFunction<T extends function>(x: T): T;
declare function assertFunction(x: not function): explodes;
from flow.
Ah, it would also be very cool if we could infer a proof-carrying boolean. Consider this trivial example.
function isNull(val) { return val == null }
function safeFormatNumber(x: ?number) {
if (isNull(x)) {
return "";
} else {
return x.toFixed(2); // no error
}
}
We didn't annotate isNull
as carrying a refinement, but Flow could infer it. Maybe this is obvious behavior, but I don't think anyone has spelled it out yet :)
from flow.
@zaygraveyard this helps only with the most basic custom type predicates/refinement use-cases.
What I believe this issue is really about is implementing something like what is called user-defined type guards in typescript, that is a way to define a type predicate isFoo(x)
which is trusted by flow
to be true only when x
is of type Foo
, thus allowing flow
to refine x
to Foo
type wherever isFoo(x)
is true.
// Pretend this is an `opaque type Email = string` imported from another module
class Email {}
// Here you really want to tell flow that `isEmail(x) === true` if and only if
// `x` is of type `Email`, and this is not what `%checks` currently does
function isEmail(x: mixed): boolean %checks {
return typeof x === 'string' && x.includes('@');
}
function spam(e: Email) {}
function main(s: string) {
if (isEmail(s)) {
spam(s);
// ERROR: `string` type is incompatible with the expected param type of `Email`
} else {
throw new TypeError(`Invalid email: ${s}`);
}
}
EDIT:
No doubt this was already mentioned above, but this comment makes it seem like this issue has been finally addressed. I actually jumped to such conclusion after reading it and was then disillusioned, so I had to reply to at least make it clear to others that this is not really the case.
from flow.
It seems like the %checks
doesn't work for libraries like lodash which was in the original case.
The flow-typed definition might look like this:
isString(value: string): true;
isString(value: any): false;
which looks like a strong enough definition for flow to infer, but it isn't.
I found the definitions for Array.isArray
and they just define boolean -
Line 278 in a610efd
and it looks like you special case it within the code.
Wouldn't it be possible to look at the typed value and then infer the types in an if so we could do
const s: string | number = '1';
if (_.isString(s)) {
// s is a string
}
without needing any syntax changes?
from flow.
I just make problems; I don't fix them. :) I agree this probably isn't super trivial to fix.
from flow.
Yeah, it seems like the x is y
syntax is a boolean type with additional information (predicates that hold when the boolean is true).
This system is a good 80% solution, I think. We wouldn't be able to annotate the type of invariant
, for example, because it returns void or blows up. I think that's OK.
I agree that flow should check that the return type is boolean, and it should also check that the truthiness of the returned value entails the correct refinement.
So, this would be bad:
function isFunction(x: any): x is Function {
if (typeof value === 'function') {
return false; // backwards
}
return true;
}
from flow.
Hey, guys. Any movements on this?
from flow.
@lukeapage your
isString(value: string): true;
isString(value: any): false;
looks lovely. It's like another way to say isString(value: any): value is String
without involving additional syntax, like TS do. That's interesting.
from flow.
We now have type guards
from flow.
It looks like TypeScript ultimately decided on predicate function syntax, should flow try to use the same syntax?
function isB(p1: any): p1 is B;
(p1: any) => p1 is B;
Where the syntax in the return type could be Type | Predicate
Where Predicate := Identifier "is" Type
from flow.
@leebyron Cool! Can you provide a link to this discussion on TypeScript's side of things?
It's possible that Flow might have different capabilities and constraints around refinements that could affect the syntax here. For example, refinements can be anded and ored together, and flow can refine specific keys within object types.
Also, it seems like a function would always carry a (maybe void) return type, and optionally a refinement. Not either/or. For example, _.isFunction(x)
still returns a boolean, but if the return value is true, x is an array. If it's false, x is not an array, and flow can use that information, too.
from flow.
That's a great point. I imagine that would work similarly to flow's current return type annotation rules. If your isNull
function is inline to the file, hopefully flow would just figure that out for you. If you export function isNull
then you should be required to annotate.
from flow.
Looks like OP covered two issues at once:
- Refinements by third-party functions.
- Aliasing:
utilities like Object.assign and React.PropTypes seem to have hardcoded behavior at the AST level, so they don't work when aliased or given alternative names
Both issues are bothering me from time to time. It would be really cool to have a way to hint type-checker for that kind of stuff. I like to alias functions in local namespace to give my code cleaner and more idiomatic look. Sadly, declarations like:
var assign = Object.assign
var isObject = require('lodash/isObject')
won't work as OP already mentioned.
For aliasing I tried to help type-checker by putting manual types, like:
var assign :typeof Object.assign = Object.assign
// or
var assign :Object$Assign = Object.assign
But Flow got one interesting pecularity: if it can't infer type properly, in most cases it can't do it at all, even if you put some hints by yourself. In most cases this hints will only yield and explicit error (which is good afterall).
Really like to see this implemented, since aliasing and abstraction is in the nature of JS which idioms Flow tries to respect. ๐
from flow.
very interested on this too
from flow.
TypeScript supports custom type guards feature in a very nice manner:
function isFish (pet: Fish | Bird): pet is Fish { โฆ }
// ^ arg is Type
from flow.
That plugin changes the control flow semantics of the JS language itself, it seems unlikely that Flow could handle arbitrary changes like that.
Of course I can use { foo && foo.bar } but looks much more clean, especially if the body of if is not so short.
Seems like an argument to split the code into a new function, rather than introduce custom control flow.
from flow.
Such developer does not care how a babel plugin works (introduces custom control flow or not). He cares about
people who don't use male pronouns are developers too, jfyi โจ
from flow.
declare function isFunction(x: function): true;
declare function isFunction(x: not function): false;
Don't you only need to add support for not
to get this working?
from flow.
@apsavin this is not a guard, foo.bar
is going to be evaluated unconditionally
from flow.
@apsavin Will jsx it won't work even with type guard. It's just a value and value in the same block. They do not rely on each other and their factories can accept anything.
from flow.
@apsavin your JSX gets transpiled to something like React.createElement(If, {condition: foo}, foo.bar)
.
Which means that foo.bar
is accessed regardless of the value of foo
.
Thus, if foo
is null
you'll get a runtime error TypeError: Cannot read property 'bar' of null
.
I would suggest using following:
{
foo
? foo.bar
: null
}
or simply:
{foo && foo.bar}
from flow.
@vkurchatkin The only thing that Flow can do to make this work is support custom source preprocessing
Having flow consume an ESTree-compatible AST would open up that possibility, and much more.
from flow.
@philipp-sorin %checks
is now supported in flow, see the documentation of functions.
But still no support for functions that throw like assert ๐ข
from flow.
Going through some issue backlogs so I apologies if I'm closing this issue prematurely but I believe at least the original issue has been addressed. Specifically,
function isFunction(a): boolean %checks {
return typeof a === 'function';
}
function concat(a: (() => null) | number) {
if (isFunction(a)) {
a();
}
}
from flow.
This also would allow to remove hardcoded invariant
declare function invariant(cond: boolean, message: string): empty %asserts(cond)
/cc @panagosg7
from flow.
The way this is tackled in things like core.typed is through a shorthand syntax for predicates on types. For example (ann function? (Pred Function))
defines a boolean return type with type refinement implications for the function? declaration. The full annotation would be along the lines of:
(ann function? (All [x] (IFn [x -> Boolean
:filters {:then (is Function 0) :else (! Function 0)}])))
Where 0 refers to the first argument (x), and specifies refinements in each branch based on the return value. I don't think you'd ever get annotations like this to be particularly clean, though making them available certainly reduces the amount of special casing/privileged forms.
(I'm not really sure how you'd translate this kind of syntax to Flow, though having functions from types-to-types would seem to be valuable, so that declare function isFunction<T>(T x): Predicate(function);
could return the properly annotated Boolean return type)
from flow.
TypeScript developers and users are currently discussing a similar feature in issue 1007
from flow.
On a related note, since:
typeof null === 'object'
constraining types with code such as:
if (typeof o === 'object') {
o.someProperty;
}
is not sufficient, whereas a simple _.isObject(o)
would be.
I was actually really surprised Flow didn't catch the null issue I had in code such as the above, and only found it during testing.
from flow.
microsoft/TypeScript#1007 is the issue discussing this, implemented in microsoft/TypeScript#3262.
https://github.com/Microsoft/TypeScript/pull/3262/files#diff-b09312f5c227031dfe4692c34364e2f0R16 this file is full of parser test cases.
from flow.
TypeScript's behavior seems to be that functions which return these type predicate refinements actually always return booleans.
I would propose that within the function, flow actually checks to ensure any possible "true" return agrees with the annotated type refinement.
Example, this should typecheck:
function isFunction(value: any): value is Function {
if (typeof value === 'function') {
return true;
}
return false;
}
var value: any = () => {};
if (isFunction(value)) {
value();
}
This should fail:
function isFunction(value: any): value is Function {
if (typeof value === 'function') {
return true;
}
// Error: isFunction must return boolean
}
var value: any = () => {};
if (isFunction(value)) {
value();
}
And this should fail:
function isFunction(value: any): value is Function {
return true; // flow doesn't think "value" is Function here.
}
var value: any = () => {};
if (isFunction(value)) {
value();
}
from flow.
Totally, flow should catch that backwards predicate.
This system is a good 80% solution, I think. We wouldn't be able to annotate the type of invariant, for example, because it returns void or blows up. I think that's OK.
Yeah, I think this is okay too. Invariant does something a little bit different anyhow, it may make sense to be a different scope of work.
...
invariant(isFunction(value));
value();
flow should be able to type check this correctly as well.
from flow.
I'm still interested in this feature, and it'd be great if @gabelevi could chime in about whether we want to move forward with this syntax. If we do, we'd need to build in support with Babel to strip the type. Currently x is y
in the type position is a parse error.
from flow.
It would be interesting if there were some way for the refinements to be associated across multiple functions. For example a has/get scenario:
class Wrapper<V> {
...
get(): ?V {
return this._v;
}
has(): boolean {
return this._v != null;
}
}
unwrap(wrapper: Wrapper<string>): string {
return wrapper.has() ? wrapper.get() : 'default_value';
}
from flow.
For me will be nice following syntax:
function isNeededThing(aaa: predicate): boolean {
if(...) {
aaa: SomeType = aaa;
return true;
}
return false;
}
from flow.
Right now at work and I can give more clarification.
More complex example:
function isNeededThing(testObject: some<T1 | T2 | T3>): boolean {
if(...) {
testObject: T2 = testObject;
return true;
} else if (...) {
testObject: T1 = testObject;
return true;
}
testObject: T3 = testObject;
}
if (isNeededThing(someObj)) {
// here someObj may became one of the types T1 | T2
} else {
// here someObj became T3
}
I offer to add some
keyword. It is new type likes mixed
, but flow
pointed that this type MAY be described below via self-assignment
. Eg. testObject: T1 = testObject;
, which have no sense for JS, and may be stripped out by optimizers.
In generally self-assignment
gives the missing stuff. And it pins type of the variable from its definition to down in the scope.
And some
keyword points to export derived type to upper scope.
Another example, only with self-assignment
:
const callbackList: FunctionT[] = [];
if (isFunction(cb)) {
cb: FunctionT = cb;
callbackList.push(cb);
}
from flow.
@nodkz If I'm understanding your examples right, then I don't think that idea adds anything that the x is Type
-return type idea couldn't do. You could possibly write isNeededThing this way instead:
function isNeededThing(testObject: T1|T2|T3): testObject is T1|T2 {
return testObject instanceof T1 || testObject instanceof T2;
}
if (isNeededThing(someObj)) {
// here someObj is T1 | T2
} else {
// here someObj is T3
}
Also consider that your way can't have the function be described from a declaration, unlike the x is Type
-return type idea. There's not enough information here to be usable:
declare function isNeededThing(testObject: some<T1 | T2 | T3>): boolean;
from flow.
I really like this proposal, and read all the comments but may not understand everything, so apologies for stupid things in advance ๐
Best would ofcourse be to have it infer it automatically, but if that doesn't always work out (and for other things like exits or errors?), what about making special wrapper types for that that work with Tagged Unions?
declare function isObject<T>(value: T): $TypeCast<T: Object, true> | $TypeCast<T: any, false>;
where TypeCast's definition would be something like $TypeCast< (an actual type cast that is true when this type is returned ), (the type that it is bound to) >
.
This would benefit even more from a negate-type, that would allow to communicate something like "If this type is returned, T is not an Object"
This would also make it (maybe something-ish) possible to have invariant working like
declare function invariant(predicate: $Throw<Error, false> | true): void;
although I am not sure if the $Throw
in the arguments are clear enough that it could return it...
also would need a Truthy
or Falsy
type to fully describe invariant :-/
And process.exit would be
...
declare function exit(status: number): $Exit<void>;
...
EDIT:
Actually, Falsy is just type Falsy = '' | null | void | 0 | false;
(Okay without NaN)
EDIT EDIT:
Invariant could also type it's effect in the return value using overloading
declare function invariant(predicate: Falsy, message: string): $Throw<Error, void>;
declare function invariant(predicate: any, message: string): void;
from flow.
What is the state of this? :)
from flow.
When the general problem is solved, please remember to add Buffer.isBuffer
from flow.
I have a functional type checking library that operates like this:
const USER_DATA_SCHEMA = is.matchingSchema({
id: is.string,
name: is.string,
dob: is.optional(number),
moreData: {
foo: is.number,
bar: is.boolean
}
});
// USER_DATA_SCHEMA returns true iff called with an argument of the form:
type UserData = {
id: string,
name: string,
dob?: number,
moreData: {
foo: number,
bar: boolean
}
};
const IS_USER = is.instanceOf(User);
// IS_USER returns true iff called with an argument of the form:
User // class defined elsewhere
Would flow support the arbitrarily deep nesting, or will this predicate feature just work with primitives? Would be awesome to have runtime and static type checking without having to write both manually.
from flow.
@avikchaudhuri any updates?
from flow.
An example from closed #4057 when I need to declare a custom guard for JSX:
<If condition={foo}>
{foo.bar}
</If>
from flow.
@vkurchatkin yes, and it's a problem and I need a way to declare a guard <If>
to handle this problem.
from flow.
@zaygraveyard No, I'll not get TypeError: Cannot read property 'bar' of null
. Read about jsx-control-statements.
Of course I can use { foo && foo.bar }
but <If></if>
looks much more clean, especially if the body of if
is not so short.
@TrySound Do you recommend to create another issue about my case?
from flow.
@apsavin Ah I see, If
is not a real component but more like a macro that gets transpiled away by the mentioned babel plugin.
Apologies should have checked the original issue where the plugin was mentioned.
from flow.
@apsavin if this is a custom plugin, then it's not going to work with Flow.
from flow.
@vkurchatkin do you mean "it will never work with flow and flow will never provide a way to describe what's going on in code like this"?
from flow.
@vkurchatkin do you mean "it will never work with flow and flow will never provide a way to describe what's going on in code like this"?
The only thing that Flow can do to make this work is support custom source preprocessing
from flow.
Seems like an argument to split the code into a new function, rather than introduce custom control flow.
I understand your point.
But try to look on it from a developer of interfaces point of view. Such developer does not care how a babel plugin works (introduces custom control flow or not). He cares about readability of his code. So he uses JSX instead of plain JavaScript. So he uses <If>
(instead of {variable && ...}
or moving parts of JSX tree into a new function) when it leads to better readability.
from flow.
Such developer does not care how a babel plugin works (introduces custom control flow or not).
Totally fair, but there are many ways you can write code that isn't compatible with Flow. Tons of JS code is written making assumptions about the data returned based on knowledge of an API, but there is only so much Flow can do to know the types for given variables without actually executing the code. In this case, it has no way it can know that foo.bar
is only executed if it exists, because you've adding logic in a side channel that it doesn't know about. If you want to use Flow, you need to make sure your code is written so that Flow can work on it. That applies in this case, but it also applies in many others.
from flow.
@apsavin Flow understands javascript and jsx semantics. This plugin changes it, so the code is not compatible with flow anymore. For example, you can make a plugin that rewrites a + b
as a(b)
. How is Flow supposed to work with this?
from flow.
If you want to use Flow, you need to make sure your code is written so that Flow can work on it.
Agree, but I think it's my responsibility to tell Flow developes about such cases (when I can't use Flow).
knowledge of an API
I can declare types for 3-rd party API and Flow will understand it perfectly
How is Flow supposed to work with this?
Sure, there's no way right now. But, maybe, ideas like
Having flow consume an ESTree-compatible AST
can help us in future.
Isn't this conversation deviating quite far from the purpose of this issue?
Totally agree. I reopened my original issue. Everyone who want to discuss - welcome to #4057
from flow.
Any progress here?
from flow.
@futpib if you briefly look through the thread above, you could see that this idea was already mentioned.
from flow.
@lukeapage See #4196 for why true | false
is currently incompatible with boolean
.
from flow.
what is the right way of doing this on Flow?
from flow.
what is the right way of doing this on Flow?
There is no current way to write % asserts
conditions.
Regarding non-throwing predicate functions, refer to %checks
in the https://flow.org/en/docs/types/functions/ documentation.
from flow.
Not really fixed, because it's still impossible to write meaningful type-checking functions that don't or can't use typeof
or instanceof
:
function isRegex(thing : mixed): boolean %checks {
return toString.call(thing) === '[object RegExp]';
}
function match(regex : mixed, str : string) {
if (isRegex(regex)) {
regex.test(str);
}
}
7: regex.test(str);
^ Cannot call `regex.test` because property `test` is missing in mixed [1].
References:
5: function match(regex : mixed, str : string) {
^ [1]
This encourages instanceof
to be overused, which is often the wrong choice.
Here's a real-world example from React: https://overreacted.io/how-does-react-tell-a-class-from-a-function/
One caveat to the
instanceof
solution is that it doesnโt work when there are multiple copies of React on the page, and the component weโre checking inherits from another React copyโsReact.Component
.
Ideally flow should have a way to type a isReactComponent
function.
from flow.
@vicapow if such a function's body may contain only single expression and must be based on typeof that is not a resolution. The whole purpose of this feature is to indicate to Flow your specific type approaches, they may not rely on prototype chain or primitives.
Like, for instance, I'm using Symbols for my types and they often have MyType.is(value: any): boolean
which does the check, and not relying on typeof or instanceof.
Other examples by @bluepnume in topic above.
from flow.
It would be great to see this implemented, hopefully we can get it soon.
from flow.
Related Issues (20)
- Packages entry points support
- Cannot resolve module node:fs HOT 1
- Record types should refine Object.values HOT 2
- [flow-remove-types] doesn't remove some types HOT 1
- flow_parser.js file increased from 0.7M to 2.7M in v0.210 HOT 4
- `Omit` does not work well with unions HOT 3
- โ๏ธ Known unsoundness issues
- Partial type does not accept plain empty object literal as a default HOT 5
- Cannot return class type as T: Cannot return `root(...)` because a call signature declaring the expected parameter / return type is missing in `SomeModel` [1] but exists in function type [2].Flow(prop-missing) HOT 2
- Does not narrow down type after undefined/falsy check HOT 1
- Recursive/deep Partial HOT 2
- Bugs in negated `instanceof` expressions HOT 1
- [Question]: How to fix flowgen test after flow update HOT 3
- [Discussion]: Any plans to make Flow more interpopble with TS ecosystem? HOT 5
- Higher order type parameter support HOT 1
- Readme links to 404
- Strange index signature error if you use $Keys<O> instead of the equivalent literal union inside $ReadOnlyArray HOT 1
- [homebrew] automation script needs update HOT 2
- "Unxepcted identifier, expected the token ;" when using await inside of ES module HOT 3
- Can't iterate through chars in a string HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from flow.