Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 b868f61
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 218 deletions.
96 changes: 95 additions & 1 deletion newtests/autocomplete/test.js
Expand Up @@ -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]),
Expand Down
10 changes: 8 additions & 2 deletions src/typing/type_annotation.ml
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
65 changes: 64 additions & 1 deletion tests/autocomplete/autocomplete.exp
Expand Up @@ -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":[
{
Expand Down
78 changes: 77 additions & 1 deletion tests/function/function.exp
Expand Up @@ -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].
Expand Down Expand Up @@ -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].
Expand All @@ -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].
Expand All @@ -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].
Expand All @@ -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].
Expand Down Expand Up @@ -627,4 +703,4 @@ References:



Found 46 errors
Found 51 errors

0 comments on commit b868f61

Please sign in to comment.