Might be the same issue as https://github.com/facebook/flow/issues/3005, but this example demonstrates the issue far more concisely:
const f: number => number = x => x + 1;
console.log(f.thisPropertyDoesNotExist); // <-- surely this should raise an error.
All of these variants fail to fail:
// Many ways of defining an increment function:
function f(x) { return x + 1; }
function g(x: number): number { return x + 1; }
const h = x => x + 1;
const i = (x: number) => x + 1;
const j: number => number = x => x + 1;
// ... but none of these property accesses raise errors.
console.log(f.thisPropertyDoesNotExist);
console.log(g.thisPropertyDoesNotExist);
console.log(h.thisPropertyDoesNotExist);
console.log(i.thisPropertyDoesNotExist);
console.log(j.thisPropertyDoesNotExist);
// ... but for objects and arrays, non-existent property access is correctly detected.
const o: {} = {};
const a = [];
console.log(o.thisPropertyDoesNotExist); // this correctly raises an error
console.log(a.thisPropertyDoesNotExist); // this correctly raises an error
https://flowtype.org/try/#0GYVwdgxgLglg9mABMAFADwJSIN6IE4CmUIeSaiA1IgIwDciAvgFCiSwKIDm6AXImCAC2AIwJ4MfASLE58REmUo16zCAgDOURAAtEAXkTk9APkNK6TNWE2IY+xL35DR4-afJULVmwCtJzmRMnaTx7I3dzWiYmAHoYxAA5BAJEOGBEKG0CdRS8AEMYHMQxPDg8dQA6Sw04ABsCCtq4bmAKzMKABVKABzEoAE8AEThspKgAUTRCqAx6aus6hqbuTjbtTp6+oZH1Mcnp2cR59UXG5pRtNY24XrwB4dG4CanNQ+PT5ZQYK-Uum62HrsnvtXnNvB9zj4fn9bvcdnsXjM5sctHA+NgGPYMVFvFo8vYANoAXSi73qZ24cGhmzu20ezwOYJq5M+eWp-1pgIRjKOQA
Even if there was a compelling reason why accessing an unknown property of a function object shouldn't be considered an error, would it be possible to add an opt-in mechanism for a stricter interpretation of a function type, where such property accesses would be forbidden, either in the type declaration or as a config option on flow itself?
This is really interesting!
To get a sense of why this happened, I wanted to see if the error was thrown for a full Object, rather than just {}:
const o1: Object = {};
const o2: {} = {};
o1.doesntExist; // Sure enough, this doesn't error. So full Objects are allowed to have random keys.
o2.doesntExist; // Does error!
So maybe it's because Functions inherit from Object that they allow arbitrary keys.
Which gave me another idea! It's actually possible to give a type to functions that _specifically do_ have a specific set of expected properties also hanging off their object, like this:
type FunctionWithProp = {
(n: number): number,
prop: bool
}
let x = (x) => x;
x.prop = true;
x.doesntExist // Doesn't error
let y: FunctionWithProp = x; // Type checks!
y.doesntExist // Errors!
So, what happens if we define a function using that syntax, but don't specify any properties:
type JustFunction = {
(n: number): number,
}
let x: JustFunction = (x) => x;
x.stuff; // Errors!
This seems to be what we want! So hopefully that helps and you can use that syntax for cases where you really want just the function and no properties. And I think this issue can be closed as this isn't a bug, just some slightly surprising consequences of the way Flow works.
As an addendum, this causes me to wonder whether the default version of Function isn't what should be used when inferring types on modern-stye JS code. Instead, maybe we should use a type that behaves like:
type JustFunction<A, B> = {
(in: A): B
}
(except it would need to work with multi-arg functions and various other crazy things). This would show errors if you just tried to fetch a property from a plain old function.
Heres a Flow try to show this is still an issue:
https://flow.org/try/#0DYUwLgBAZgrgdgYwgXggCgJQoHwQN4C+A3AFAmyIB0YAFiHGpjvgRkUA
Just shipped a production error because of this the other day, so still a problem in the latest version.
Flow repro in latest version
Same code in TS correctly catches this issue
Duplicate of #106
Most helpful comment
Just shipped a production error because of this the other day, so still a problem in the latest version.
Flow repro in latest version
Same code in TS correctly catches this issue