Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Member expression refinement #8072

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,12 @@ and _json_of_use_t_impl json_cx t =
| SetPropT (_, _, name, _, t, _)
| GetPropT (_, _, name, t)
| MatchPropT (_, _, name, t)
| TestPropT (_, _, name, t) ->
| TestPropT (_, _, _, name, t) ->
[("propRef", json_of_propref json_cx name); ("propType", _json_of_t json_cx t)]
| SetPrivatePropT (_, _, name, _, _, t, _)
| GetPrivatePropT (_, _, name, _, _, t) ->
[("propRef", JSON_String name); ("propType", _json_of_t json_cx t)]
| TestElemT (_, _, _, indext, elemt)
| SetElemT (_, _, indext, elemt, _)
| GetElemT (_, _, indext, elemt) ->
[("indexType", _json_of_t json_cx indext); ("elemType", _json_of_t json_cx elemt)]
Expand Down Expand Up @@ -501,6 +502,7 @@ and _json_of_use_t_impl json_cx t =
| ElemT (_, _, base, action) ->
[ ("baseType", _json_of_t json_cx base);
(match action with
| TestElem (_, t) -> ("testElem", _json_of_t json_cx t)
| ReadElem t -> ("readElem", _json_of_t json_cx t)
| WriteElem (t, _) -> ("writeElem", _json_of_t json_cx t)
| CallElem (_, funtype) -> ("callElem", json_of_funcalltype json_cx funtype)) ]
Expand Down Expand Up @@ -1905,6 +1907,7 @@ and dump_use_t_ (depth, tvars) cx t =
(String.concat "; " (Core_list.map ~f:(fun (x, _) -> x) (SMap.bindings tmap))))
| ExportTypeT _ -> p t
| AssertExportIsTypeT _ -> p t
| TestElemT (_, _, _, ix, etype)
| GetElemT (_, _, ix, etype) -> p ~extra:(spf "%s, %s" (kid ix) (kid etype)) t
| GetKeysT _ -> p t
| GetValuesT _ -> p t
Expand Down Expand Up @@ -2003,7 +2006,7 @@ and dump_use_t_ (depth, tvars) cx t =
~extra:
(spf "%s, %s, %s" (string_of_use_op use_op) (object_kit resolve_tool tool) (kid tout))
t
| TestPropT (_, _, prop, ptype) -> p ~extra:(spf "(%s), %s" (propref prop) (kid ptype)) t
| TestPropT (_, _, _, prop, ptype) -> p ~extra:(spf "(%s), %s" (propref prop) (kid ptype)) t
| ThisSpecializeT (_, this, _) -> p ~extra:(spf "%s" (kid this)) t
| ToStringT (_, arg) -> p ~extra:(use_kid arg) t
| UnaryMinusT _ -> p t
Expand Down
53 changes: 34 additions & 19 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ let error_message_kind_of_upper = function
| MethodT (_, _, _, Computed t, _, _) -> Error_message.IncompatibleMethodT (loc_of_t t, None)
| CallT _ -> Error_message.IncompatibleCallT
| ConstructorT _ -> Error_message.IncompatibleConstructorT
| TestElemT (_, _, _, t, _) -> Error_message.IncompatibleGetElemT (loc_of_t t)
| GetElemT (_, _, t, _) -> Error_message.IncompatibleGetElemT (loc_of_t t)
| SetElemT (_, _, t, _, _) -> Error_message.IncompatibleSetElemT (loc_of_t t)
| CallElemT (_, _, t, _) -> Error_message.IncompatibleCallElemT (loc_of_t t)
Expand Down Expand Up @@ -885,6 +886,7 @@ let object_like_op = function
| SetPropT _
| GetPropT _
| TestPropT _
| TestElemT _
| MethodT _
| LookupT _
| MatchPropT _
Expand Down Expand Up @@ -1588,7 +1590,7 @@ struct
| ExportValue
(* If it's a re-export, we can assume that the appropriate export checks have been
* applied in the original module. *)

| ReExport ->
t
(* If it's of the form `export type` then check to make sure it's actually a type. *)
Expand Down Expand Up @@ -3125,8 +3127,8 @@ struct
cx
trace
(List.hd ts, LookupT (reason, strict, List.tl ts @ try_ts_on_failure, s, t))
| (IntersectionT _, TestPropT (reason, _, prop, tout)) ->
rec_flow cx trace (l, GetPropT (unknown_use, reason, prop, tout))
| (IntersectionT _, TestPropT (use_op, reason, _, prop, tout)) ->
rec_flow cx trace (l, GetPropT (use_op, reason, prop, tout))
(* extends **)
| (IntersectionT (_, rep), ExtendsUseT (use_op, reason, try_ts_on_failure, l, u)) ->
let (t, ts) = InterRep.members_nel rep in
Expand Down Expand Up @@ -4120,7 +4122,7 @@ struct
* function component and class component
*)
| ( DefT (r, _, ReactAbstractComponentT _),
(TestPropT _ | GetPropT _ | SetPropT _ | GetElemT _ | SetElemT _) ) ->
(TestPropT _ | TestElemT _ | GetPropT _ | SetPropT _ | GetElemT _ | SetElemT _) ) ->
let statics = get_builtin_type cx ~trace r "React$AbstractComponentStatics" in
rec_flow cx trace (statics, u)
(******************)
Expand Down Expand Up @@ -5444,6 +5446,10 @@ struct
| (DefT (reason_s, trust, StrT _), GetElemT (use_op, reason_op, index, tout)) ->
rec_flow cx trace (index, UseT (use_op, NumT.why reason_s |> with_trust bogus_trust));
rec_flow_t cx trace (StrT.why reason_op trust, tout)
| (DefT (reason_s, trust, StrT _), TestElemT (use_op, reason_op, id, index, tout)) ->
Context.test_prop_hit cx id;
rec_flow cx trace (index, UseT (use_op, NumT.why reason_s |> with_trust bogus_trust));
rec_flow_t cx trace (StrT.why reason_op trust, tout)
(* Expressions may be used as keys to access objects and arrays. In
general, we cannot evaluate such expressions at compile time. However,
in some idiomatic special cases, we can; in such cases, we know exactly
Expand All @@ -5458,6 +5464,8 @@ struct
rec_flow cx trace (key, ElemT (use_op, reason_op, l, WriteElem (tin, tout)))
| ((DefT (_, _, (ObjT _ | ArrT _)) | AnyT _), GetElemT (use_op, reason_op, key, tout)) ->
rec_flow cx trace (key, ElemT (use_op, reason_op, l, ReadElem tout))
| ((DefT (_, _, (ObjT _ | ArrT _)) | AnyT _), TestElemT (use_op, reason_op, id, key, tout)) ->
rec_flow cx trace (key, ElemT (use_op, reason_op, l, TestElem (id, tout)))
| ( (DefT (_, _, (ObjT _ | ArrT _)) | AnyT _),
CallElemT (reason_call, reason_lookup, key, ft) ) ->
let action = CallElem (reason_call, ft) in
Expand All @@ -5472,6 +5480,7 @@ struct
in
(match action with
| ReadElem t -> rec_flow cx trace (obj, GetPropT (use_op, reason_op, propref, t))
| TestElem (id, t) -> rec_flow cx trace (obj, TestPropT (use_op, reason_op, id, propref, t))
| WriteElem (tin, tout) ->
rec_flow cx trace (obj, SetPropT (use_op, reason_op, propref, Normal, tin, None));
Option.iter ~f:(fun t -> rec_flow_t cx trace (obj, t)) tout
Expand Down Expand Up @@ -5557,6 +5566,7 @@ struct
match action with
(* These are safe to do with tuples and unknown indexes *)
| ReadElem _
| TestElem _
| CallElem _ ->
()
(* This isn't *)
Expand Down Expand Up @@ -6275,20 +6285,22 @@ struct
know what the type of the property would be, we set things up so that the
result of the read cannot be used in any interesting way. *)
(**************************************************************************)
| (DefT (_, _, NullT), TestPropT (reason_op, _, propref, tout)) ->
| (DefT (_, _, NullT), TestPropT (use_op, reason_op, _, propref, tout)) ->
(* The wildcard TestPropT implementation forwards the lower bound to
LookupT. This is unfortunate, because LookupT is designed to terminate
(successfully) on NullT, but property accesses on null should be type
errors. Ideally, we should prevent LookupT constraints from being
syntax-driven, in order to preserve the delicate invariants that
surround it. *)
rec_flow cx trace (l, GetPropT (unknown_use, reason_op, propref, tout))
| (DefT (r, trust, MixedT (Mixed_truthy | Mixed_non_maybe)), TestPropT (_, id, _, tout)) ->
rec_flow cx trace (l, GetPropT (use_op, reason_op, propref, tout))
| (DefT (_, _, NullT), TestElemT (use_op, reason_op, _, t, tout)) ->
rec_flow cx trace (l, GetElemT (use_op, reason_op, t, tout))
| (DefT (r, trust, MixedT (Mixed_truthy | Mixed_non_maybe)), (TestPropT (_, _, id, _, tout) | TestElemT (_, _, id, _, tout))) ->
(* Special-case property tests of definitely non-null/non-void values to
return mixed and treat them as a hit. *)
Context.test_prop_hit cx id;
rec_flow_t cx trace (DefT (r, trust, MixedT Mixed_everything), tout)
| (_, TestPropT (reason_op, id, propref, tout)) ->
| (_, TestPropT (use_op, reason_op, id, propref, tout)) ->
(* NonstrictReturning lookups unify their result, but we don't want to
unify with the tout tvar directly, so we create an indirection here to
ensure we only supply lower bounds to tout. *)
Expand Down Expand Up @@ -6338,7 +6350,7 @@ struct
lookup_kind,
[],
propref,
ReadProp { use_op = unknown_use; obj_t = l; tout } ) )
ReadProp { use_op; obj_t = l; tout } ) )
(************)
(* indexing *)
(************)
Expand Down Expand Up @@ -7119,7 +7131,7 @@ struct
(* Propagation cases: these cases don't use the fact that the LHS is
empty, but they propagate the LHS to other types and trigger additional
flows that may need to occur. *)

| (_, UseT (_, DefT (_, _, PolyT _)))
| (_, UseT (_, TypeAppT _))
| (_, UseT (_, AnyWithLowerBoundT _))
Expand Down Expand Up @@ -7156,6 +7168,7 @@ struct
| (_, ObjTestProtoT _)
| (_, OptionalChainT _)
| (_, SentinelPropTestT _)
| (_, TestElemT _)
| (_, TestPropT _) ->
false
(* Error prevention: we should succeed because otherwise we'll hit
Expand All @@ -7177,7 +7190,7 @@ struct
types; either the flow would succeed anyways or it would fall
through to the final catch-all error case and cause a spurious
error. *)

| (_, UseT _)
| (_, ArrRestT _)
| (_, CallElemT _)
Expand Down Expand Up @@ -7294,7 +7307,7 @@ struct
true
| UseT (use_op, DefT (_, _, ArrT (ROArrayAT t)))
(* read-only arrays are covariant *)

| UseT (use_op, DefT (_, _, ClassT t)) (* mk_instance ~for_type:false *)
| UseT (use_op, ExactT (_, t))
| UseT (use_op, OpenPredT (_, t, _, _))
Expand Down Expand Up @@ -7390,8 +7403,9 @@ struct
| SpecializeT _
| SubstOnPredT _
(* Should be impossible. We only generate these with OpenPredTs. *)

| TestPropT _
| TestElemT _
| ThisSpecializeT _
| ToStringT _
| UnaryMinusT _
Expand All @@ -7402,11 +7416,11 @@ struct
| UseT (_, OptionalT _) (* used to filter optional *)
| ObjAssignFromT _
(* Handled in __flow *)

| ObjAssignToT _ (* Handled in __flow *)
| UseT (_, ThisTypeAppT _)
(* Should never occur, so we just defer to __flow to handle errors *)

| UseT (_, InternalT _)
| UseT (_, MatchingPropT _)
| UseT (_, DefT (_, _, IdxWrapper _))
Expand All @@ -7416,11 +7430,11 @@ struct
(* Ideally, any would pollute every member of the union. However, it should be safe to only
taint the type in the branch that flow picks when generating constraints for this, so
this can be handled by the pre-existing rules *)

| UseT (_, UnionT _)
| UseT (_, IntersectionT _)
(* Already handled in the wildcard case in __flow *)

| UseT (_, OpenT _) ->
false
(* These types have no t_out, so can't propagate anything. Thus we short-circuit by returning
Expand Down Expand Up @@ -10552,7 +10566,7 @@ struct
*)
| (_, [])
(* No more arguments *)

| ([], _) ->
([], arglist, parlist)
| (tin :: tins, (name, tout) :: touts) ->
Expand Down Expand Up @@ -10765,7 +10779,7 @@ struct
* errors, this is bad. Instead, let's degrade array literals to `any` *)
| `Literal
(* There is no AnyTupleT type, so let's degrade to `any`. *)

| `Tuple ->
AnyT.untyped reason_op
else
Expand Down Expand Up @@ -11052,6 +11066,7 @@ struct
end

and perform_elem_action cx trace ~use_op reason_op l value = function
| TestElem (_, t)
| ReadElem t ->
let loc = aloc_of_reason reason_op in
rec_flow_t cx trace (reposition cx ~trace loc value, t)
Expand Down
2 changes: 2 additions & 0 deletions src/typing/sigHash.ml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ type hash =
| MatchPropH
| GetPrivatePropH
| TestPropH
| TestElemH
| SetElemH
| GetElemH
| CallElemH
Expand Down Expand Up @@ -328,6 +329,7 @@ let hash_of_use_ctor =
| MatchPropT _ -> MatchPropH
| GetPrivatePropT _ -> GetPrivatePropH
| TestPropT _ -> TestPropH
| TestElemT _ -> TestElemH
| SetElemT _ -> SetElemH
| GetElemT _ -> GetElemH
| CallElemT _ -> CallElemH
Expand Down
20 changes: 15 additions & 5 deletions src/typing/statement.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3966,7 +3966,7 @@ and subscript =
let reason = mk_reason (RProperty None) loc in
let (((_, tind), _) as index) = expression cx index in
let use_op = Op (GetProperty (mk_expression_reason ex)) in
let opt_use = OptGetElemT (use_op, reason, tind) in
let opt_use = get_elem_opt_use ~is_cond reason ~use_op tind in
begin
match opt_state with
| NonOptional ->
Expand Down Expand Up @@ -5929,8 +5929,11 @@ and predicates_of_condition cx e =
{
Member._object;
property =
Member.PropertyIdentifier
(prop_loc, ({ Ast.Identifier.name = prop_name; comments = _ } as id));
(Member.PropertyIdentifier
(prop_loc, { Ast.Identifier.name = prop_name; comments = _ })
| Member.PropertyExpression
(prop_loc, Ast.Expression.Literal
{ Ast.Literal.value = Ast.Literal.String prop_name; _ } )) as property;
} ) ->
let (((_, obj_t), _) as _object_ast) =
match _object with
Expand All @@ -5956,7 +5959,8 @@ and predicates_of_condition cx e =
let use_op = Op (GetProperty (mk_expression_reason e)) in
get_prop ~is_cond:true cx expr_reason ~use_op obj_t (prop_reason, prop_name)
in
let property = Member.PropertyIdentifier ((prop_loc, t), id) in
let m = new loc_mapper t in
let property = m#member_property property in
let ast = ((loc, t), Member { Member._object = _object_ast; property }) in
let out =
match Refinement.key e with
Expand Down Expand Up @@ -6159,10 +6163,16 @@ and get_private_field cx reason ~use_op tobj name =
*)
and get_prop_opt_use ~is_cond reason ~use_op (prop_reason, name) =
if is_cond then
OptTestPropT (reason, mk_id (), Named (prop_reason, name))
OptTestPropT (use_op, reason, mk_id (), Named (prop_reason, name))
else
OptGetPropT (use_op, reason, Named (prop_reason, name))

and get_elem_opt_use ~is_cond reason ~use_op tind =
if is_cond then
OptTestElemT (use_op, reason, mk_id (), tind)
else
OptGetElemT (use_op, reason, tind)

and get_prop ~is_cond cx reason ~use_op tobj (prop_reason, name) =
let opt_use = get_prop_opt_use ~is_cond reason ~use_op (prop_reason, name) in
Tvar.mk_where cx reason (fun t ->
Expand Down