Skip to content
Permalink
Browse files

Make function type statics stricter

Summary:
Before this change, we optimistically typed the statics of a function type as
`any`, under the assumption that a function would not be used as an object. In
practice, our optimism here is unfounded.

This diff changes the type of the statics object for function types to an empty,
inexact object type with Function.prototype for its __proto__. This choice is
sound with respect to the runtime behaviors while also allowing functions with
statics to be valid subtypes via width subtyping.

Reviewed By: panagosg7

Differential Revision: D14448564

fbshipit-source-id: 87f6bf50a795760023e1e237856e80587df9f857
  • Loading branch information...
samwgoldman authored and facebook-github-bot committed Apr 26, 2019
1 parent 3c5a410 commit b868f61026b9b34cb22c3f59e1bcbb65a614c71b
@@ -1162,7 +1162,101 @@ export default suite(({addFile, flowCmd}) => [
).stdout(
`
{
"result": []
"result": [
{
"name": "hasOwnProperty",
"type": "(prop: any) => boolean",
"func_details": {
"return_type": "boolean",
"params": [
{
"name": "prop",
"type": "any"
}
]
},
"path": "[LIB] core.js",
"line": 66,
"endline": 66,
"start": 5,
"end": 38
},
{
"name": "isPrototypeOf",
"type": "(o: any) => boolean",
"func_details": {
"return_type": "boolean",
"params": [
{
"name": "o",
"type": "any"
}
]
},
"path": "[LIB] core.js",
"line": 67,
"endline": 67,
"start": 5,
"end": 34
},
{
"name": "propertyIsEnumerable",
"type": "(prop: any) => boolean",
"func_details": {
"return_type": "boolean",
"params": [
{
"name": "prop",
"type": "any"
}
]
},
"path": "[LIB] core.js",
"line": 68,
"endline": 68,
"start": 5,
"end": 44
},
{
"name": "toLocaleString",
"type": "() => string",
"func_details": {
"return_type": "string",
"params": []
},
"path": "[LIB] core.js",
"line": 69,
"endline": 69,
"start": 5,
"end": 28
},
{
"name": "toString",
"type": "() => string",
"func_details": {
"return_type": "string",
"params": []
},
"path": "[LIB] core.js",
"line": 70,
"endline": 70,
"start": 5,
"end": 22
},
{
"name": "valueOf",
"type": "() => mixed",
"func_details": {
"return_type": "mixed",
"params": []
},
"path": "[LIB] core.js",
"line": 71,
"endline": 71,
"start": 5,
"end": 20
}
]
}
`,
).exitCodes([0]),
@@ -905,9 +905,14 @@ let rec convert cx tparams_map = Ast.Type.(function
| None -> None, None in

let (_, return_t), _ as return_ast = convert cx tparams_map return in
let statics_t =
let reason = replace_reason (fun d -> RStatics d) reason in
Obj_type.mk_with_proto cx reason (FunProtoT reason)
~sealed:true ~exact:false ?call:None
in
let ft =
DefT (reason, annot_trust (), FunT (
dummy_static reason,
statics_t,
mk_reason RPrototype loc |> Unsoundness.function_proto_any,
{
this_t = bound_function_dummy_this;
@@ -921,7 +926,8 @@ let rec convert cx tparams_map = Ast.Type.(function
}))
in
let id = Context.make_nominal cx in
(loc, poly_type_of_tparams id tparams ft),
let t = poly_type_of_tparams id tparams ft in
(loc, t),
Function {
Function.params = (params_loc, {
Function.Params.params = List.rev rev_param_asts;
@@ -703,7 +703,70 @@ union.js = {
}
object_builtins.js = {"error":"not enough type information to autocomplete","result":[]}
function_builtins.js = {"error":"not enough type information to autocomplete","result":[]}
fun.js = {"result":[]}
fun.js = {
"result":[
{
"name":"hasOwnProperty",
"type":"(prop: any) => boolean",
"func_details":{"return_type":"boolean","params":[{"name":"prop","type":"any"}]},
"path":"[LIB] core.js",
"line":66,
"endline":66,
"start":5,
"end":38
},
{
"name":"isPrototypeOf",
"type":"(o: any) => boolean",
"func_details":{"return_type":"boolean","params":[{"name":"o","type":"any"}]},
"path":"[LIB] core.js",
"line":67,
"endline":67,
"start":5,
"end":34
},
{
"name":"propertyIsEnumerable",
"type":"(prop: any) => boolean",
"func_details":{"return_type":"boolean","params":[{"name":"prop","type":"any"}]},
"path":"[LIB] core.js",
"line":68,
"endline":68,
"start":5,
"end":44
},
{
"name":"toLocaleString",
"type":"() => string",
"func_details":{"return_type":"string","params":[]},
"path":"[LIB] core.js",
"line":69,
"endline":69,
"start":5,
"end":28
},
{
"name":"toString",
"type":"() => string",
"func_details":{"return_type":"string","params":[]},
"path":"[LIB] core.js",
"line":70,
"endline":70,
"start":5,
"end":22
},
{
"name":"valueOf",
"type":"() => mixed",
"func_details":{"return_type":"mixed","params":[]},
"path":"[LIB] core.js",
"line":71,
"endline":71,
"start":5,
"end":20
}
]
}
this.js = {
"result":[
{
@@ -181,6 +181,20 @@ References:
^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------------ bind.js:7:7

Cannot call `y` with `123` bound to `b` because number [1] is incompatible with string [2].

bind.js:7:7
7| y(123); // error, number !~> string
^^^ [1]

References:
bind.js:4:30
4| function(x: (a: string, b: string) => void) {
^^^^^^ [2]


Error ----------------------------------------------------------------------------------------------------- bind.js:14:7

Cannot call `y` with `123` bound to `b` because number [1] is incompatible with string [2].
@@ -439,6 +453,23 @@ References:
^^^^ [2]


Error ------------------------------------------------------------------------------------------------- function.js:38:6

Cannot cast `y.length` to undefined because number [1] is incompatible with undefined [2].

function.js:38:6
38| (y.length: void); // error, it's a number
^^^^^^^^

References:
<BUILTINS>/core.js:116:13
116| length: number;
^^^^^^ [1]
function.js:38:16
38| (y.length: void); // error, it's a number
^^^^ [2]


Error ------------------------------------------------------------------------------------------------- function.js:41:6

Cannot cast `x.name` to undefined because string [1] is incompatible with undefined [2].
@@ -456,6 +487,23 @@ References:
^^^^ [2]


Error ------------------------------------------------------------------------------------------------- function.js:42:6

Cannot cast `y.name` to undefined because string [1] is incompatible with undefined [2].

function.js:42:6
42| (y.name: void); // error, it's a string
^^^^^^

References:
<BUILTINS>/core.js:117:11
117| name: string;
^^^^^^ [1]
function.js:42:14
42| (y.name: void); // error, it's a string
^^^^ [2]


Error ------------------------------------------------------------------------------------------------ function.js:48:16

Cannot assign `'foo'` to `x.length` because string [1] is incompatible with number [2].
@@ -470,6 +518,20 @@ References:
^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------ function.js:49:16

Cannot assign `'foo'` to `y.length` because string [1] is incompatible with number [2].

function.js:49:16
49| y.length = 'foo'; // error, it's a number
^^^^^ [1]

References:
<BUILTINS>/core.js:116:13
116| length: number;
^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------ function.js:52:14

Cannot assign `123` to `x.name` because number [1] is incompatible with string [2].
@@ -484,6 +546,20 @@ References:
^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------ function.js:53:14

Cannot assign `123` to `y.name` because number [1] is incompatible with string [2].

function.js:53:14
53| y.name = 123; // error, it's a string
^^^ [1]

References:
<BUILTINS>/core.js:117:11
117| name: string;
^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------ issue-7529.js:4:7

Cannot call `foo` with `123` bound to `x` because number [1] is incompatible with string [2].
@@ -627,4 +703,4 @@ References:



Found 46 errors
Found 51 errors

0 comments on commit b868f61

Please sign in to comment.
You can’t perform that action at this time.