You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
then Flow knows that the code within the if statement is a function. This doesn't work if you instead call a helper function like Underscore's _.isFunction. Similarly, 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.
Will it be possible to teach Flow about these sorts of functions so that it's possible to use these third-party libraries without changing code too much?
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;
Reacted by Matt, Wolfram Kriesing, Nathan Broadbent, William Carroll and Gajus Kuizinas
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)
@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.
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.
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.
Ah, it would also be very cool if we could infer a proof-carrying boolean. Consider this trivial example.
functionisNull(val){returnval==null}functionsafeFormatNumber(x: ?number){if(isNull(x)){return"";}else{returnx.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 :)
Reacted by Ryan Ashcraft, Aspen Smith, Shawn Morel, Asa Zernik and Gajus Kuizinas
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.
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,
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’s React.Component.
Ideally flow should have a way to type a isReactComponent function.
@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.
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.
Activity
gabelevi commentedon Nov 18, 2014
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, ifisFunction(x)returnstrue, then x is a function. Otherwise it is not a function. So maybe something likeBut that's kind of verbose. Maybe overloading can help?
Ideally you'd also be able to declare when certain functions will throw, like in an
assertFunction(x)functionsophiebits commentedon Nov 18, 2014
I just make problems; I don't fix them. :) I agree this probably isn't super trivial to fix.
c-spencer commentedon Nov 19, 2014
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: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)spion commentedon Nov 20, 2014
TypeScript developers and users are currently discussing a similar feature in issue 1007
jareware commentedon Dec 16, 2014
On a related note, since:
constraining types with code such as:
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.
leebyron commentedon Jul 30, 2015
It looks like TypeScript ultimately decided on predicate function syntax, should flow try to use the same syntax?
Where the syntax in the return type could be
Type | PredicateWhere
Predicate := Identifier "is" Typesamwgoldman commentedon Jul 31, 2015
@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.leebyron commentedon Jul 31, 2015
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.
leebyron commentedon Jul 31, 2015
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:
This should fail:
And this should fail:
samwgoldman commentedon Jul 31, 2015
Yeah, it seems like the
x is ysyntax 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:
leebyron commentedon Jul 31, 2015
Totally, flow should catch that backwards predicate.
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.
flow should be able to type check this correctly as well.
samwgoldman commentedon Jul 31, 2015
Ah, it would also be very cool if we could infer a proof-carrying boolean. Consider this trivial example.
We didn't annotate
isNullas carrying a refinement, but Flow could infer it. Maybe this is obvious behavior, but I don't think anyone has spelled it out yet :)leebyron commentedon Jul 31, 2015
That's a great point. I imagine that would work similarly to flow's current return type annotation rules. If your
isNullfunction is inline to the file, hopefully flow would just figure that out for you. If youexport function isNullthen you should be required to annotate.46 remaining items
StreetStrider commentedon Jun 4, 2018
@lukeapage your
looks lovely. It's like another way to say
isString(value: any): value is Stringwithout involving additional syntax, like TS do. That's interesting.sibelius commentedon Jul 12, 2018
what is the right way of doing this on Flow?
gajus commentedon Jul 14, 2018
There is no current way to write
% assertsconditions.Regarding non-throwing predicate functions, refer to
%checksin the https://flow.org/en/docs/types/functions/ documentation.vicapow commentedon Feb 1, 2019
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,
bluepnume commentedon Feb 1, 2019
Not really fixed, because it's still impossible to write meaningful type-checking functions that don't or can't use
typeoforinstanceof:This encourages
instanceofto 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/
Ideally flow should have a way to type a
isReactComponentfunction.StreetStrider commentedon Feb 1, 2019
@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): booleanwhich does the check, and not relying on typeof or instanceof.Other examples by @bluepnume in topic above.
TrySound commentedon Feb 1, 2019
Agree,
%checksis half working solution.montogeek commentedon Apr 16, 2019
It would be great to see this implemented, hopefully we can get it soon.
goodmind commentedon Aug 15, 2019
This also would allow to remove hardcoded invariant
/cc @panagosg7
leebyron commentedon Sep 16, 2019
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.microsoft/TypeScript#32695
I'd still love to see both features in Flow!
gajus commentedon Nov 28, 2019
Would be nice to see this prioritised.
SamChou19815 commentedon Jul 1, 2023
We now have type guards