Skip to content
Permalink
Browse files

Add support for [[call]] internal slot properties

Summary:
In the previous commit, I deprecated the use of `$call` properties. Some uses
can be subsumed by callable property syntax, but not all. The existing callable
property syntax can only be used with function types, for example. This syntax
can be used with any type.

Unlike deprecated `$call` syntax, the `[[call]]` syntax will define an
overloaded call signature if combined with callable properties or other
`[[call]]` properties.

This syntax can also be used to resolve the ambiguity static callable
properties in declared classes. `declare class C { static (): void }`
currently defines a callable class, but it could also be a class with an
instance method named `static`. This diff preserves the existing behavior.

Reviewed By: panagosg7

Differential Revision: D8042915

fbshipit-source-id: 70111db89295e593999ed5999a90c178aeeb168a
  • Loading branch information...
samwgoldman authored and facebook-github-bot committed Jun 11, 2018
1 parent df8ee96 commit 954a72704a6338778c940239573045b28c716488
Showing with 194 additions and 15 deletions.
  1. +40 −14 src/typing/type_annotation.ml
  2. +125 −1 tests/call_properties/call_properties.exp
  3. +29 −0 tests/call_properties/internal_slot.js
@@ -862,13 +862,26 @@ let rec convert cx tparams_map = Ast.Type.(function
add_dict loc i o, ts, spread
| Object.Property (loc, p) ->
add_prop loc p o, ts, spread
| Object.InternalSlot (loc, { Object.InternalSlot.id = (_, name); _ }) ->
Flow.add_output cx FlowError.(
EUnsupportedSyntax (loc, UnsupportedInternalSlot {
name;
static = false;
}));
o, ts, spread
| Object.InternalSlot (loc, slot) ->
let { Object.InternalSlot.
id = (_, name);
value;
static=_; (* object props are never static *)
optional;
_method=_;
} = slot in
if name = "call" then
let t = convert cx tparams_map value in
let t = if optional then Type.optional t else t in
add_call t o, ts, spread
else (
Flow.add_output cx FlowError.(
EUnsupportedSyntax (loc, UnsupportedInternalSlot {
name;
static = false;
}));
o, ts, spread
)
| Object.SpreadProperty (_, { Object.SpreadProperty.argument }) ->
let ts = match o with
| None -> ts
@@ -1192,13 +1205,26 @@ and add_interface_properties cx tparams_map properties s =

)

| InternalSlot (loc, { InternalSlot.id = (_, name); static; _ }) ->
Flow.add_output cx Flow_error.(
EUnsupportedSyntax (loc, UnsupportedInternalSlot {
name;
static;
}));
x
| InternalSlot (loc, slot) ->
let { InternalSlot.
id = (_, name);
value;
optional;
static;
_method=_;
} = slot in
if name = "call" then
let t = convert cx tparams_map value in
let t = if optional then Type.optional t else t in
append_call ~static t x
else (
Flow.add_output cx Flow_error.(
EUnsupportedSyntax (loc, UnsupportedInternalSlot {
name;
static;
}));
x
)

| SpreadProperty (loc, _) ->
Flow.add_output cx Flow_error.(EInternal (loc, InterfaceTypeSpread));
@@ -327,6 +327,130 @@ Deprecated $call syntax. Use callable property syntax instead. (`deprecated-call
^^^^^


Error --------------------------------------------------------------------------------------------- internal_slot.js:5:2

Cannot cast object literal to `O` because a callable signature is missing in object literal [1] but exists in `O` [2].

internal_slot.js:5:2
5| ({}: O); // err: no callable property
^^ [1]

References:
internal_slot.js:5:6
5| ({}: O); // err: no callable property
^ [2]


Error --------------------------------------------------------------------------------------------- internal_slot.js:6:2

Cannot cast function to `O` because number [1] is incompatible with undefined [2] in the return value.

internal_slot.js:6:2
6| (function() { return 0 }: O); // err: number ~> void
^^^^^^^^^^^^^^^^^^^^^^^

References:
internal_slot.js:6:22
6| (function() { return 0 }: O); // err: number ~> void
^ [1]
internal_slot.js:2:15
2| [[call]](): void;
^^^^ [2]


Error -------------------------------------------------------------------------------------------- internal_slot.js:13:2

Cannot cast object literal to `I` because a callable signature is missing in object literal [1] but exists in `I` [2].

internal_slot.js:13:2
13| ({}: I); // err: no callable property
^^ [1]

References:
internal_slot.js:13:6
13| ({}: I); // err: no callable property
^ [2]


Error -------------------------------------------------------------------------------------------- internal_slot.js:14:2

Cannot cast function to `I` because number [1] is incompatible with undefined [2] in the return value.

internal_slot.js:14:2
14| (function() { return 0 }: I); // err: number ~> void
^^^^^^^^^^^^^^^^^^^^^^^

References:
internal_slot.js:14:22
14| (function() { return 0 }: I); // err: number ~> void
^ [1]
internal_slot.js:10:15
10| [[call]](): void;
^^^^ [2]


Error -------------------------------------------------------------------------------------------- internal_slot.js:20:2

Cannot cast `C1()` to empty because undefined [1] is incompatible with empty [2].

internal_slot.js:20:2
20| (C1(): empty); // error: void ~> empty
^^^^

References:
internal_slot.js:18:22
18| static [[call]](): void;
^^^^ [1]
internal_slot.js:20:8
20| (C1(): empty); // error: void ~> empty
^^^^^ [2]


Error -------------------------------------------------------------------------------------------- internal_slot.js:23:1

Cannot call `mixed_callable` because mixed [1] is not a function.

internal_slot.js:23:1
23| mixed_callable();
^^^^^^^^^^^^^^^^

References:
internal_slot.js:22:41
22| declare var mixed_callable: { [[call]]: mixed };
^^^^^ [1]


Error ------------------------------------------------------------------------------------------- internal_slot.js:28:16

Cannot call `annot_callable` with `0` bound to the first parameter because number [1] is incompatible with string [2].

internal_slot.js:28:16
28| annot_callable(0); // error: number ~> string
^ [1]

References:
internal_slot.js:26:11
26| type Fn = string => number;
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- internal_slot.js:29:2

Cannot cast `annot_callable(...)` to empty because number [1] is incompatible with empty [2].

internal_slot.js:29:2
29| (annot_callable("foo"): empty); // error: number ~> empty
^^^^^^^^^^^^^^^^^^^^^

References:
internal_slot.js:26:21
26| type Fn = string => number;
^^^^^^ [1]
internal_slot.js:29:25
29| (annot_callable("foo"): empty); // error: number ~> empty
^^^^^ [2]


Error --------------------------------------------------------------------------------------------------- use_ops.js:4:2

Cannot cast `a` to `B` because a callable signature is missing in object type [1] but exists in function type [2] in
@@ -346,7 +470,7 @@ References:



Found 23 errors
Found 31 errors

Only showing the most relevant union/intersection branches.
To see all branches, re-run Flow with --show-all-branches
@@ -0,0 +1,29 @@
type O = {
[[call]](): void;
}

({}: O); // err: no callable property
(function() { return 0 }: O); // err: number ~> void
(function() {}: O); // ok

interface I {
[[call]](): void;
}

({}: I); // err: no callable property
(function() { return 0 }: I); // err: number ~> void
(function() {}: I); // ok

declare class C1 {
static [[call]](): void;
}
(C1(): empty); // error: void ~> empty

declare var mixed_callable: { [[call]]: mixed };
mixed_callable();

declare var annot_callable: { [[call]]: Fn }
type Fn = string => number;
(annot_callable("foo"): number); // OK
annot_callable(0); // error: number ~> string
(annot_callable("foo"): empty); // error: number ~> empty

0 comments on commit 954a727

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