Expression-based languages tend to have pattern matching which provides another way to solve the same problem.
Flow typing is most useful in imperative languages where code like this is common:
if (foo is! Bar) return "not a Bar";
foo.someBarMethod();
Early returns and other imperative control flow is idiomatic and it's annoying if the static type system doesn't understand it.In a more functional or expression-oriented language, early returns and other imperative control flow like that is rarer, so there's less "flowing" to type over.
Also, what? Pattern matching differentiate between values of a single type, and while the assignment mechanism is a great nice-to-have, it still completely follows the basic type consistency that actually typed functions provide.
Statements have none of these qualities..
Why is it a problem that statements are "devoid of return information"? A statement occurs in a position where, by definition, no value it produces will be used.
Some of the most used languages with flow typing have statements and follow them for typing, so that is emphatically not the reason.
(Specifically for this point: “and follow them for typing”)
Most languages have embraced statements over expressions for a lot of language constructs.
This causes a lot of issues:
An if-statement is in essence a function from boolean to unit/() (i.e. from a barely useful type to a type with no useful information), while an if-expressions will contain enough type information to at least provide this kind of “flow typing”, (even if it induces an amount of boolean blindness.)
It’s even worse when you get to loop-statements, which often is a function from unit/() to (unit/() | bottom/⊥), compared to a map or a reduce or fold, which again provide enough type information to provide this kind of “flow typing”.
Sometimes you will of course need to provide impure conditionals or loops, but that can be made explicit in the type system.
IMHO statements in programming languages are an anti-pattern.