diff --git a/src/typing/debug.ml b/src/typing/debug.ml index 67211da91f7..62dcf2cc1b0 100644 --- a/src/typing/debug.ml +++ b/src/typing/debug.ml @@ -21,7 +21,17 @@ let string_of_pred_ctor = function | LeftP _ -> "LeftP" | RightP _ -> "RightP" | ExistsP -> "ExistsP" - | IsP _ -> "IsP" + | TrueP -> "TrueP" + | FalseP -> "FalseP" + | VoidP -> "VoidP" + | NullP -> "NullP" + | MaybeP -> "MaybeP" + | BoolP -> "BoolP" + | StrP -> "StrP" + | NumP -> "NumP" + | FunP -> "FunP" + | ObjP -> "ObjP" + | ArrP -> "ArrP" let string_of_binary_test_ctor = function | Instanceof -> "Instanceof" @@ -554,8 +564,19 @@ and json_of_pred_impl json_cx p = Json.( "binaryTest", json_of_binary_test json_cx b; "type", _json_of_t json_cx t ] - | ExistsP -> [] - | IsP s -> ["typeName", JString s] + | TrueP + | FalseP + | ExistsP + | VoidP + | NullP + | MaybeP + | BoolP + | StrP + | NumP + | FunP + | ObjP + | ArrP + -> [] )) and json_of_binary_test json_cx = check_depth json_of_binary_test_impl json_cx diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index b10f9197af6..64e44d404dd 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -4322,131 +4322,131 @@ and predicate cx trace t (l,p) = match (l,p) with (* typeof _ ~ "boolean" *) (***********************) - | (MixedT r, IsP "boolean") -> + | (MixedT r, BoolP) -> rec_flow cx trace (BoolT.why r, t) - | (_, IsP "boolean") -> + | (_, BoolP) -> filter cx trace t l is_bool - | (_, NotP(IsP "boolean")) -> + | (_, NotP(BoolP)) -> filter cx trace t l (not_ is_bool) (***********************) (* typeof _ ~ "string" *) (***********************) - | (MixedT r, IsP "string") -> + | (MixedT r, StrP) -> rec_flow cx trace (StrT.why r, t) - | (_, IsP "string") -> + | (_, StrP) -> filter cx trace t l is_string - | (_, NotP(IsP "string")) -> + | (_, NotP(StrP)) -> filter cx trace t l (not_ is_string) (***********************) (* typeof _ ~ "number" *) (***********************) - | (MixedT r, IsP "number") -> + | (MixedT r, NumP) -> rec_flow cx trace (NumT.why r, t) - | (_, IsP "number") -> + | (_, NumP) -> filter cx trace t l is_number - | (_, NotP(IsP "number")) -> + | (_, NotP(NumP)) -> filter cx trace t l (not_ is_number) (***********************) (* typeof _ ~ "function" *) (***********************) - | (MixedT r, IsP "function") -> + | (MixedT r, FunP) -> rec_flow cx trace (AnyFunT (replace_reason "function" r), t) - | (_, IsP "function") -> + | (_, FunP) -> filter cx trace t l is_function - | (_, NotP(IsP "function")) -> + | (_, NotP(FunP)) -> filter cx trace t l (not_ is_function) (***********************) (* typeof _ ~ "object" *) (***********************) - | (MixedT r, IsP "object") -> + | (MixedT r, ObjP) -> let obj = AnyObjT (replace_reason "object" r) in let union = create_union [NullT.why r; obj] in rec_flow cx trace (union, t) - | (_, IsP "object") -> + | (_, ObjP) -> filter cx trace t l is_object - | (_, NotP(IsP "object")) -> + | (_, NotP(ObjP)) -> filter cx trace t l (not_ is_object) (*******************) (* Array.isArray _ *) (*******************) - | (MixedT r, IsP "array") -> + | (MixedT r, ArrP) -> let filtered_l = ArrT (replace_reason "array" r, AnyT.why r, []) in rec_flow cx trace (filtered_l, t) - | (_, IsP "array") -> + | (_, ArrP) -> filter cx trace t l is_array - | (_, NotP(IsP "array")) -> + | (_, NotP(ArrP)) -> filter cx trace t l (not_ is_array) (***********************) (* typeof _ ~ "undefined" *) (***********************) - | (_, IsP "undefined") -> + | (_, VoidP) -> rec_flow cx trace (filter_undefined l, t) - | (_, NotP(IsP "undefined")) -> + | (_, NotP(VoidP)) -> rec_flow cx trace (filter_not_undefined l, t) (********) (* null *) (********) - | (_, IsP "null") -> + | (_, NullP) -> rec_flow cx trace (filter_null l, t) - | (_, NotP(IsP "null")) -> + | (_, NotP(NullP)) -> rec_flow cx trace (filter_not_null l, t) (*********) (* maybe *) (*********) - | (_, IsP "null or undefined") -> + | (_, MaybeP) -> rec_flow cx trace (filter_maybe l, t) - | (_, NotP(IsP "null or undefined")) -> + | (_, NotP(MaybeP)) -> rec_flow cx trace (filter_not_maybe l, t) (********) (* true *) (********) - | (_, IsP "true") -> + | (_, TrueP) -> rec_flow cx trace (filter_true l, t) - | (_, NotP(IsP "true")) -> + | (_, NotP(TrueP)) -> rec_flow cx trace (filter_not_true l, t) (*********) (* false *) (*********) - | (_, IsP "false") -> + | (_, FalseP) -> rec_flow cx trace (filter_false l, t) - | (_, NotP(IsP "false")) -> + | (_, NotP(FalseP)) -> rec_flow cx trace (filter_not_false l, t) (************************) @@ -4460,7 +4460,9 @@ and predicate cx trace t (l,p) = match (l,p) with rec_flow cx trace (filter_not_exists l, t) (* unreachable *) - | (_, (NotP _ | IsP _)) -> + | (_, NotP (NotP _)) + | (_, NotP (AndP _)) + | (_, NotP (OrP _)) -> assert_false (spf "Unexpected predicate %s" (string_of_predicate p)) and binary_predicate cx trace sense test left right result = @@ -5657,9 +5659,19 @@ and gc_pred cx state = function | NotP (p) -> gc_pred cx state p - | ExistsP -> () - - | IsP _ -> () + | TrueP + | FalseP + | ExistsP + | NullP + | MaybeP + | BoolP + | FunP + | NumP + | ObjP + | StrP + | VoidP + | ArrP + -> () (* Keep a reachable type variable around. *) let live cx state id = diff --git a/src/typing/type.ml b/src/typing/type.ml index 27d2b4a8795..b50cf5babe9 100644 --- a/src/typing/type.ml +++ b/src/typing/type.ml @@ -315,11 +315,20 @@ and predicate = | LeftP of binary_test * t | RightP of binary_test * t - (* truthy *) - | ExistsP + | ExistsP (* truthy *) + | TrueP (* true *) + | FalseP (* false *) + | NullP (* null *) + | MaybeP (* null or undefined *) + + | BoolP (* boolean *) + | FunP (* function *) + | NumP (* number *) + | ObjP (* object *) + | StrP (* string *) + | VoidP (* undefined *) - (* typeof, null check, Array.isArray *) - | IsP of string + | ArrP (* Array.isArray *) and binary_test = (* e1 instanceof e2 *) @@ -735,7 +744,19 @@ let rec loc_of_predicate = function -> loc_of_t t | ExistsP - | IsP _ + | TrueP + | FalseP + | NullP + | MaybeP + + | BoolP + | FunP + | NumP + | ObjP + | StrP + | VoidP + + | ArrP -> Loc.none (* TODO!!!!!!!!!!!! *) @@ -979,4 +1000,18 @@ let rec string_of_predicate = function spf "right operand of %s with left operand = %s" (string_of_binary_test b) (desc_of_t t) | ExistsP -> "truthy" - | IsP s -> s + | TrueP -> "true" + | FalseP -> "false" + | NullP -> "null" + | MaybeP -> "null or undefined" + + (* typeof *) + | VoidP -> "undefined" + | BoolP -> "boolean" + | StrP -> "string" + | NumP -> "number" + | FunP -> "function" + | ObjP -> "object" + + (* Array.isArray *) + | ArrP -> "array" diff --git a/src/typing/type_inference_js.ml b/src/typing/type_inference_js.ml index e3950d409bb..97ed2775cd9 100644 --- a/src/typing/type_inference_js.ml +++ b/src/typing/type_inference_js.ml @@ -4714,9 +4714,9 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( | Some name, t -> match op with | Binary.Equal | Binary.NotEqual -> - Some (name, t, IsP "null or undefined", op = Binary.Equal) + Some (name, t, MaybeP, op = Binary.Equal) | Binary.StrictEqual | Binary.StrictNotEqual -> - Some (name, t, IsP "null", op = Binary.StrictEqual) + Some (name, t, NullP, op = Binary.StrictEqual) | _ -> None in match refinement with @@ -4731,9 +4731,9 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( | Some name, t -> match op with | Binary.Equal | Binary.NotEqual -> - Some (name, t, IsP "null or undefined", op = Binary.Equal) + Some (name, t, MaybeP, op = Binary.Equal) | Binary.StrictEqual | Binary.StrictNotEqual -> - Some (name, t, IsP "undefined", op = Binary.StrictEqual) + Some (name, t, VoidP, op = Binary.StrictEqual) | _ -> None in match refinement with @@ -4748,8 +4748,8 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( match op with (* TODO support == *) | Binary.StrictEqual | Binary.StrictNotEqual -> - let pred = string_of_bool right in - Some (name, t, IsP pred, op = Binary.StrictEqual) + let pred = if right then TrueP else FalseP in + Some (name, t, pred, op = Binary.StrictEqual) | _ -> None in match refinement with @@ -4774,10 +4774,27 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( in (* inspect a typeof equality test *) - let typeof_test sense arg typename = + let typeof_test loc sense arg typename str_loc = match refinable_lvalue arg with - | Some name, t -> result BoolT.t name t (IsP typename) sense - | None, t -> empty_result BoolT.t + | Some name, t -> + let pred = match typename with + | "boolean" -> Some BoolP + | "function" -> Some FunP + | "number" -> Some NumP + | "object" -> Some ObjP + | "string" -> Some StrP + | "undefined" -> Some VoidP + | _ -> None + in + begin match pred with + | Some pred -> result BoolT.t name t pred sense + | None -> + let reason = mk_reason (spf "string literal `%s`" typename) str_loc in + let err = "This value is not a valid `typeof` return value" in + Flow_js.add_warning cx [reason, err]; + empty_result (BoolT.at loc) + end + | None, t -> empty_result (BoolT.at loc) in let mk_and map1 map2 = Scope.KeyMap.merge @@ -4905,32 +4922,32 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( bool_test loc op right value (* typeof expr ==/=== string *) - | _, Binary { Binary.operator = Binary.Equal | Binary.StrictEqual; + | loc, Binary { Binary.operator = Binary.Equal | Binary.StrictEqual; left = _, Unary { Unary.operator = Unary.Typeof; argument; _ }; - right = _, Literal { Literal.value = Literal.String s; _ } + right = str_loc, Literal { Literal.value = Literal.String s; _ } } -> - typeof_test true argument s + typeof_test loc true argument s str_loc (* typeof expr !=/!== string *) - | _, Binary { Binary.operator = Binary.NotEqual | Binary.StrictNotEqual; + | loc, Binary { Binary.operator = Binary.NotEqual | Binary.StrictNotEqual; left = _, Unary { Unary.operator = Unary.Typeof; argument; _ }; - right = _, Literal { Literal.value = Literal.String s; _ } + right = str_loc, Literal { Literal.value = Literal.String s; _ } } -> - typeof_test false argument s + typeof_test loc false argument s str_loc (* string ==/=== typeof expr *) - | _, Binary { Binary.operator = Binary.Equal | Binary.StrictEqual; - left = _, Literal { Literal.value = Literal.String s; _ }; + | loc, Binary { Binary.operator = Binary.Equal | Binary.StrictEqual; + left = str_loc, Literal { Literal.value = Literal.String s; _ }; right = _, Unary { Unary.operator = Unary.Typeof; argument; _ } } -> - typeof_test true argument s + typeof_test loc true argument s str_loc (* string !=/!== typeof expr *) - | _, Binary { Binary.operator = Binary.NotEqual | Binary.StrictNotEqual; - left = _, Literal { Literal.value = Literal.String s; _ }; + | loc, Binary { Binary.operator = Binary.NotEqual | Binary.StrictNotEqual; + left = str_loc, Literal { Literal.value = Literal.String s; _ }; right = _, Unary { Unary.operator = Unary.Typeof; argument; _ } } -> - typeof_test false argument s + typeof_test loc false argument s str_loc (* expr.name ===/!== value *) | loc, Binary { Binary. @@ -4968,7 +4985,7 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( } -> ( match refinable_lvalue arg with | Some name, t -> - result BoolT.t name t (IsP "array") true + result BoolT.t name t ArrP true | None, t -> empty_result BoolT.t ) @@ -4998,7 +5015,7 @@ and predicates_of_condition cx type_params_map e = Ast.(Expression.( } in match refinable_lvalue fake_ast with | Some name, t -> - result (BoolT.at loc) name t (IsP "undefined") false + result (BoolT.at loc) name t VoidP false | None, t -> empty_result (BoolT.at loc) ) diff --git a/tests/refinements/refinements.exp b/tests/refinements/refinements.exp index 54e0ee9be23..728cf558bb6 100644 --- a/tests/refinements/refinements.exp +++ b/tests/refinements/refinements.exp @@ -323,6 +323,9 @@ typeof.js:30:5,18: call of method `stuff` Function cannot be called on possibly undefined value [LIB] lib.js:3:27,45: undefined +typeof.js:49:20,24: string literal `foo` +This value is not a valid `typeof` return value + undef.js:31:13,13: null This type is incompatible with undef.js:31:13,20: number @@ -407,4 +410,4 @@ void.js:85:7,12: undefined This type is incompatible with void.js:85:7,19: number -Found 95 errors +Found 96 errors diff --git a/tests/refinements/typeof.js b/tests/refinements/typeof.js index 78390d348fd..207c89fe784 100644 --- a/tests/refinements/typeof.js +++ b/tests/refinements/typeof.js @@ -44,3 +44,9 @@ function anyobj(x: number | Object): number { } return x; // OK, x refined to `number` } + +function testInvalidValue(x: mixed) { + if (typeof x === "foo") { // error + return 0; + } +}