Permalink
Browse files

Improve error messages for calls through unknown type variables

Summary: For type inference based on constraint solving, sometimes we will encounter a type variable that has not been solved. Two cases are dealt with in this diff: member access through an object with Tvar type, and static_class_id resolution of a type that is a type variable.

Reviewed By: Wilfred

Differential Revision: D12998349

fbshipit-source-id: 7089ad70855587e077e436da8ee7eb14e18beda9
  • Loading branch information...
andrewjkennedy authored and hhvm-bot committed Nov 12, 2018
1 parent 2ff4427 commit 77e36f7c36246b3bd7774e1731e98320ad5e0cc1
@@ -103,6 +103,7 @@ let rec reason = function
| Rsum_dynamic p -> Rsum_dynamic (pos p)
| Rbitwise_dynamic p -> Rbitwise_dynamic (pos p)
| Rincdec_dynamic p -> Rincdec_dynamic (pos p)
| Rtype_variable p -> Rtype_variable (pos p)
let string_id (p, x) = pos p, x
@@ -529,6 +529,8 @@ module Typing = struct
| InconsistentMutability
| InvalidMutabilityFlavorInAssignment
| OptionNull
| UnknownObjectMember
| UnknownClass
(* EXTEND HERE WITH NEW VALUES IF NEEDED *)
[@@ deriving enum, show { with_path = false } ]
let err_code = to_enum
@@ -2149,6 +2149,11 @@ let const_mutation pos1 pos2 ty =
let expected_class ?(suffix="") pos =
add (Typing.err_code Typing.ExpectedClass) pos ("Was expecting a class"^suffix)
let unknown_class pos r =
let msg = ("Was expecting a class but class is unknown") in
add_list (Typing.err_code Typing.UnknownClass)
([pos, msg] @ r)
let snot_found_hint = function
| `no_hint ->
[]
@@ -2702,6 +2707,11 @@ let non_object_member s pos1 ty pos2 =
pos2, "Check this out"
]
let unknown_object_member s pos r =
let msg = ("You are trying to access the member " ^ s ^ " on a value whose class is unknown") in
add_list (Typing.err_code Typing.UnknownObjectMember)
([pos, msg] @ r)
let non_class_member s pos1 ty pos2 =
add_list (Typing.err_code Typing.NonClassMember) [
pos1,
@@ -197,6 +197,7 @@ module type S = sig
val array_append : Pos.t -> Pos.t -> string -> unit
val const_mutation : Pos.t -> Pos.t -> string -> unit
val expected_class : ?suffix:string -> Pos.t -> unit
val unknown_class : Pos.t -> (Pos.t * string) list -> unit
val smember_not_found :
[< `class_constant | `class_variable | `static_method | `class_typeconst] ->
Pos.t ->
@@ -261,6 +262,7 @@ module type S = sig
val static_dynamic : Pos.t -> Pos.t -> string -> elt_type:string -> unit
val null_member : string -> Pos.t -> (Pos.t * string) list -> unit
val non_object_member : string -> Pos.t -> string -> Pos.t -> unit
val unknown_object_member : string -> Pos.t -> (Pos.t * string) list -> unit
val non_class_member : string -> Pos.t -> string -> Pos.t -> unit
val ambiguous_member : string -> Pos.t -> string -> Pos.t -> unit
val null_container : Pos.t -> (Pos.t * string) list -> unit
@@ -228,7 +228,10 @@ class virtual tvar_expanding_type_mapper = object(this)
method on_tvar env (r : Reason.t) n =
let env, ty = Env.get_type env r n in
this#on_type env ty
begin match ty with
| _, Tvar _ -> env, ty
| _ -> this#on_type env ty
end
method virtual on_type : env -> locl ty -> result
end
@@ -238,8 +241,12 @@ end
class virtual tvar_substituting_type_mapper = object(this)
method on_tvar (env : env) (r : Reason.t) n =
let env, ty = Env.get_type env r n in
let env, ty = this#on_type env ty in
let env = Env.add env n ty in
env, ty
begin match ty with
| _, Tvar _ -> env, ty
| _ ->
let env, ty = this#on_type env ty in
let env = Env.add env n ty in
env, ty
end
method virtual on_type : env -> locl ty -> result
end
@@ -1762,7 +1762,7 @@ and expr_
* types for its type parameters.
*)
let env, tvarl =
List.map_env env class_.tc_tparams TUtils.unresolved_tparam in
List.map_env env class_.tc_tparams (TUtils.unresolved_tparam ~use_pos:p) in
let params = List.map class_.tc_tparams begin fun (_, (p, n), _, _) ->
Reason.Rwitness p, Tgeneric n
end in
@@ -4423,8 +4423,8 @@ and class_get_ ~is_method ~is_const ~ety_env ?(explicit_tparams=[])
env, method_, None
end
)
| _, (Tnonnull | Tarraykind _ | Toption _
| Tprim _ | Tvar _ | Tfun _ | Ttuple _ | Tanon (_, _) | Tobject
| _, (Tvar _ | Tnonnull | Tarraykind _ | Toption _
| Tprim _ | Tfun _ | Ttuple _ | Tanon (_, _) | Tobject
| Tshape _) ->
(* should never happen; static_class_id takes care of these *)
env, (Reason.Rnone, Typing_utils.tany env), None
@@ -4641,14 +4641,12 @@ and obj_get_concrete_ty ~is_method ~valkind ?(explicit_tparams=[])
| _, Tany
| _, Terr ->
default ()
| _ ->
Errors.non_object_member
id_str id_pos (Typing_print.error (snd concrete_ty))
(Reason.to_pos (fst concrete_ty));
default ()
(* k_lhs takes the type of the object receiver *)
and obj_get_ ~is_method ~nullsafe ~valkind ~(pos_params : expr list option) ?(explicit_tparams=[])
env ty1 cid (id_pos, id_str as id) k k_lhs =
@@ -4726,6 +4724,11 @@ and obj_get_ ~is_method ~nullsafe ~valkind ~(pos_params : expr list option) ?(ex
| _, Toption ty -> nullable_obj_get ty
| r, Tprim Nast.Tnull ->
nullable_obj_get (r, Tany)
(* We are trying to access a member through a value of unknown type *)
| r, Tvar _ ->
Errors.unknown_object_member id_str id_pos (Reason.to_string "It is unknown" r);
k (env, (r, Typing_utils.terr env), None)
| _, _ ->
k (obj_get_concrete_ty ~is_method ~valkind ~explicit_tparams env ety1 cid id k_lhs)
@@ -4930,11 +4933,14 @@ and static_class_id ~check_constraints p env =
| _, Tabstract (AKgeneric _, _)
| _, Tclass _ -> ty
| r, Tunresolved tyl -> r, Tunresolved (List.map tyl resolve_ety)
| _, Tvar _ as ty -> resolve_ety ty
| _, Tdynamic as ty -> ty
| _, (Tany | Tprim Tstring | Tabstract (_, None) | Tobject)
when not (Env.is_strict env) ->
Reason.Rwitness p, Typing_utils.tany env
| r, Tvar _ ->
Errors.unknown_class p (Reason.to_string "It is unknown" r);
Reason.Rwitness p, Typing_utils.terr env
| _, (Terr | Tany | Tnonnull | Tarraykind _ | Toption _
| Tprim _ | Tfun _ | Ttuple _
| Tabstract ((AKenum _ | AKdependent _ | AKnewtype _), _)
@@ -80,7 +80,10 @@ let get_type env x_reason x =
let env, x = get_var env x in
let ty = IMap.get x env.tenv in
match ty with
| None -> env, (x_reason, Tany)
| None ->
if TypecheckerOptions.new_inference env.genv.tcopt
then env, (x_reason, Tvar x)
else env, (x_reason, Tany)
| Some ty -> env, ty
let get_type_unsafe env x =
@@ -438,7 +441,10 @@ let tparams_visitor env =
| _ -> acc
method! on_tvar acc r ix =
let _env, ty = get_type env r ix in
this#on_type acc ty
begin match ty with
| _, Tvar _ -> acc
| _ -> this#on_type acc ty
end
end
let get_tparams_aux env acc ty = (tparams_visitor env)#on_type acc ty
let get_tparams env ty = get_tparams_aux env SSet.empty ty
@@ -259,7 +259,7 @@ and localize_ft ~use_pos ?(instantiate_tparams=true) ?(explicit_tparams=[]) ~ety
let env, substs =
if instantiate_tparams
then
let default () = List.map_env env ft.ft_tparams TUtils.unresolved_tparam in
let default () = List.map_env env ft.ft_tparams (TUtils.unresolved_tparam ~use_pos) in
let env, tvarl =
if List.length explicit_tparams = 0
then default ()
@@ -729,7 +729,10 @@ let rec from_type: type a. Typing_env.env -> a ty -> json =
match snd ty with
| Tvar _ ->
let _, ty = Typing_env.expand_type env ty in
from_type env ty
begin match snd ty with
| Tvar _ -> obj @@ kind "var"
| _ -> from_type env ty
end
| Tarray(opt_ty1, opt_ty2) ->
obj @@ kind "array" @ args (Option.to_list opt_ty1 @ Option.to_list opt_ty2)
| Tthis ->
@@ -90,6 +90,7 @@ type t =
| Rregex of Pos.t
| Rlambda_use of Pos.t
| Rimplicit_upper_bound of Pos.t * string
| Rtype_variable of Pos.t
and arg_position =
| Aonly
@@ -227,6 +228,8 @@ let rec to_string prefix r =
| Rinstantiate (r_orig, generic_name, r_inst) ->
(to_string prefix r_orig) @
(to_string (" via this generic " ^ generic_name) r_inst)
| Rtype_variable p ->
[(p, prefix ^ " because a type could not be determined here")]
| Rarray_filter (_, r) ->
(to_string prefix r) @
[(p, "array_filter converts KeyedContainer<Tk, Tv> to \
@@ -363,6 +366,7 @@ and to_pos = function
| Rarith_ret_int p -> p
| Rbitwise_dynamic p -> p
| Rincdec_dynamic p -> p
| Rtype_variable p -> p
(* This is a mapping from internal expression ids to a standardized int.
* Used for outputting cleaner error messages to users
@@ -470,6 +474,7 @@ match r with
| Rsum_dynamic _ -> "Rsum_dynamic"
| Rbitwise_dynamic _ -> "Rbitwise_dynamic"
| Rincdec_dynamic _ -> "Rincdec_dynamic"
| Rtype_variable _ -> "Rtype_variable"
let pp fmt r =
Format.pp_print_string fmt @@ to_constructor_string r
@@ -611,8 +611,8 @@ let in_var env ty =
let env = Env.add env x ty in
env, (fst ty, Tvar x)
let unresolved_tparam env (_, (pos, _), _, _) =
let reason = Reason.Rwitness pos in
let unresolved_tparam ~use_pos env (_, (_, _), _, _) =
let reason = Reason.Rtype_variable use_pos in
if TypecheckerOptions.new_inference (Typing_env.get_tcopt env)
then env, (reason, Tvar (Env.fresh ()))
else in_var env (reason, Tunresolved [])
@@ -523,4 +523,6 @@ UnserializableType = 4292
InconsistentMutability = 4293
InvalidMutabilityFlavorInAssignment = 4294
OptionNull = 4295
UnknownObjectMember = 4296
UnknownClass = 4297
|}]
@@ -1,10 +1,10 @@
File "map_from_keys.php--file2.php", line 5, characters 43-44:
Invalid return type (Typing[4110])
File "map_from_keys.php--file1.php", line 6, characters 26-27:
File "map_from_keys.php--file2.php", line 5, characters 12-22:
This is a classname string
File "map_from_keys.php--file2.php", line 5, characters 12-45:
It was implicitly typed as a classname string during this operation
File "map_from_keys.php--file1.php", line 6, characters 22-23:
File "map_from_keys.php--file2.php", line 5, characters 12-22:
It is incompatible with a string
File "map_from_keys.php--file2.php", line 5, characters 12-45:
It was implicitly typed as a string during this operation
@@ -1,6 +1,6 @@
File "meth_caller11.php", line 31, characters 17-23:
Invalid argument (Typing[4110])
File "meth_caller11.php", line 17, characters 15-15:
File "meth_caller11.php", line 30, characters 13-41:
This is an int
File "meth_caller11.php", line 24, characters 34-45:
It was implicitly typed as an int during this operation
@@ -1,8 +1,6 @@
File "meth_caller4.php", line 23, characters 10-16:
Invalid return type (Typing[4110])
File "meth_caller4.php", line 12, characters 9-9:
This is an int
File "meth_caller4.php", line 18, characters 42-44:
It was implicitly typed as an int during this operation
This is an int
File "meth_caller4.php", line 20, characters 30-33:
It is incompatible with a bool
@@ -1 +1,6 @@
No errors
File "identity_then_invoke.php", line 20, characters 7-9:
You are trying to access the member foo on a value whose class is unknown (Typing[4296])
File "identity_then_invoke.php", line 18, characters 8-9:
It is unknown because a type could not be determined here
File "identity_then_invoke.php", line 12, characters 23-23:
via this generic T
@@ -0,0 +1,21 @@
<?hh // strict
// Copyright 2004-present Facebook. All Rights Reserved.
class B { }
class C extends B {
public static function foo(): void { }
}
class Inv<Tinv> {
public function __construct(private Tinv $item) { }
}
function id<T>(T $x): T {
return $x;
}
function bar(C $c): void {
// So: T=v, have classname<C> <: v and $x:v
$x = id(C::class);
// Here, how do we "guess" that v = classname<C>?
$x::foo();
}
@@ -0,0 +1,6 @@
File "identity_then_invoke_class.php", line 20, characters 3-4:
Was expecting a class but class is unknown (Typing[4297])
File "identity_then_invoke_class.php", line 18, characters 8-9:
It is unknown because a type could not be determined here
File "identity_then_invoke_class.php", line 12, characters 23-23:
via this generic T
@@ -9,18 +9,18 @@ class Inv<Tinv> {
public function __construct(public Tinv $item) { }
}
function make_inv<T>(T $x): (T,Inv<T>) {
return tuple($x, new Inv($x));
function make_inv<T>(T $x): Inv<T> {
return new Inv($x);
}
function setInvB(Inv<B> $ib):void {
$ib->item = new B();
}
function bar(C $c): Inv<B> {
// So: T=v, have C <: v and $x:v and $y:Inv<v>
list($x, $y) = make_inv($c);
// So: T=v, have C <: v and $x:Inv<v>
$x = make_inv($c);
// Here, how do we "guess" v?
$x->foo();
// setInvB($y);
return $y;
$x->item->foo();
// setInvB($x);
return $x;
}
@@ -1 +1,8 @@
No errors
File "make_inv_then_invoke.php", line 23, characters 13-15:
You are trying to access the member foo on a value whose class is unknown (Typing[4296])
File "make_inv_then_invoke.php", line 21, characters 8-15:
It is unknown because a type could not be determined here
File "make_inv_then_invoke.php", line 12, characters 33-33:
via this generic T
File "make_inv_then_invoke.php", line 9, characters 38-41:
via this generic Tinv
@@ -1,6 +1,6 @@
File "nullable_compare.php", line 8, characters 10-22:
This comparison has invalid types. Only comparisons in which both arguments are strings, nums, DateTime, or DateTimeImmutable are allowed (Typing[4240])
File "nullable_compare.php", line 3, characters 13-13:
File "nullable_compare.php", line 7, characters 15-17:
This is an int
File "nullable_compare.php", line 8, characters 19-22:
This is null
@@ -2,11 +2,11 @@ File "super_constraint_10.php", line 19, characters 21-25:
Some type constraint(s) here are violated (Typing[4110])
File "super_constraint_10.php", line 7, characters 42-44:
'Tk1' is a constrained type parameter
File "super_constraint_10.php", line 7, characters 42-44:
File "super_constraint_10.php", line 19, characters 21-25:
This is a value of generic type Tk1
File "super_constraint_10.php", line 19, characters 27-31:
It was implicitly typed as a value of generic type Tk1 during this operation
File "super_constraint_10.php", line 7, characters 32-34:
File "super_constraint_10.php", line 19, characters 21-25:
It is incompatible with a value of generic type Tk2
File "super_constraint_10.php", line 19, characters 34-38:
It was implicitly typed as a value of generic type Tk2 during this operation
@@ -1,6 +1,6 @@
File "super_constraint_9.php", line 12, characters 16-17:
Invalid argument (Typing[4110])
File "super_constraint_9.php", line 6, characters 17-19:
File "super_constraint_9.php", line 12, characters 10-10:
This is a mixed value
File "super_constraint_9.php", line 12, characters 10-18:
It was implicitly typed as a mixed value during this operation

0 comments on commit 77e36f7

Please sign in to comment.