Skip to content

Commit b1f24f7

Browse files
Andrew Kennedymeta-codesync[bot]
authored andcommitted
Simplify treatment of parent::__construct and fix issues with like types
Summary: The logic for checking `parent::__construct` calls is shared with that for checking `new`. It's broken in at least a couple of ways: * There is no check that a constructor exists in the parent (T38050584) * It doesn't properly support like-type calling i.e. treating the constructor as "supporting dynamic" Also, the logic is kind of nonsense: for a base class that has generics e.g. `Base<TB>` we generate fresh type variables for the generics, do all the argument checking, and then after the fact check that `Base<#1> = Base<TB>`. It's much simpler instead to treat `parent::__construct` just like a normal call. We don't need the nonsense of fresh type variables, e.g. if we have `Derived<T> extends Base<vec<T>>` then we're simply checking parameter types that involve `T` in scope. But in order to enable like-type calling, we must make sure that `supportdyn` is wrapped around the function type of the constructor, just as with methods. Note that we already check constructors under both static and dynamic assumptions, so this "use-site" `supportdyn` treatment now fits with the "definition-site" checking of the constructor. Differential Revision: D87070529 fbshipit-source-id: d37417f699d57fcacf27de9d37cc1d071a0a24ab
1 parent 1332662 commit b1f24f7

21 files changed

+167
-544
lines changed

hphp/hack/src/decl/decl_folded_class.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ let build_constructor
390390
~superfluous_override:false
391391
~dynamicallycallable:false
392392
~readonly_prop_or_needs_concrete:false
393-
~support_dynamic_type:false
393+
~support_dynamic_type:(sm_support_dynamic_type method_)
394394
~needs_init:false
395395
~no_auto_likes:(sm_no_auto_likes method_)
396396
~safe_global_variable:false;

hphp/hack/src/decl/direct_decl_smart_constructors.rs

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,35 @@ impl<'o, 't> DirectDeclSmartConstructors<'o, 't> {
24902490
)
24912491
}
24922492

2493+
fn possibly_make_supportdyn_method(
2494+
&mut self,
2495+
class_attributes: &Attributes,
2496+
method: ShallowMethod,
2497+
) -> ShallowMethod {
2498+
if ((self.implicit_sdt() && !class_attributes.no_support_dynamic_type)
2499+
|| class_attributes.support_dynamic_type
2500+
|| class_attributes.dynamically_referenced)
2501+
&& !method.flags.contains(MethodFlags::SUPPORT_DYNAMIC_TYPE)
2502+
{
2503+
let type_ = match *method.type_.1 {
2504+
Ty_::Tfun(ft) => {
2505+
let flags = ft.flags | FunTypeFlags::SUPPORT_DYNAMIC_TYPE;
2506+
let ft = FunType { flags, ..ft };
2507+
Ty(method.type_.0, Box::new(Ty_::Tfun(ft)))
2508+
}
2509+
_ => method.type_,
2510+
};
2511+
let flags = method.flags | MethodFlags::SUPPORT_DYNAMIC_TYPE;
2512+
ShallowMethod {
2513+
type_,
2514+
flags,
2515+
..method
2516+
}
2517+
} else {
2518+
method
2519+
}
2520+
}
2521+
24932522
// For a polymorphic context with form `ctx $f` (represented here as
24942523
// `Tapply "$f"`), add a type parameter named `Tctx$f`, and rewrite the
24952524
// parameter `(function (ts)[_]: t) $f` as `(function (ts)[Tctx$f]: t) $f`
@@ -4569,6 +4598,10 @@ impl<'o, 't> FlattenSmartConstructors for DirectDeclSmartConstructors<'o, 't> {
45694598
}
45704599
}
45714600
Node::Constructor(box ConstructorNode { method, properties }) => {
4601+
// Annoyingly, the <<__SupportDynamicType>> annotation on a
4602+
// class implicitly changes the decls of every constructor inside
4603+
// it, so we have to reallocate them here.
4604+
let method = self.possibly_make_supportdyn_method(&class_attributes, method);
45724605
constructor = Some(method);
45734606
for property in properties {
45744607
props.push(property)
@@ -4578,29 +4611,7 @@ impl<'o, 't> FlattenSmartConstructors for DirectDeclSmartConstructors<'o, 't> {
45784611
// Annoyingly, the <<__SupportDynamicType>> annotation on a
45794612
// class implicitly changes the decls of every method inside
45804613
// it, so we have to reallocate them here.
4581-
let method = if ((self.implicit_sdt()
4582-
&& !class_attributes.no_support_dynamic_type)
4583-
|| class_attributes.support_dynamic_type
4584-
|| class_attributes.dynamically_referenced)
4585-
&& !method.flags.contains(MethodFlags::SUPPORT_DYNAMIC_TYPE)
4586-
{
4587-
let type_ = match *method.type_.1 {
4588-
Ty_::Tfun(ft) => {
4589-
let flags = ft.flags | FunTypeFlags::SUPPORT_DYNAMIC_TYPE;
4590-
let ft = FunType { flags, ..ft };
4591-
Ty(method.type_.0, Box::new(Ty_::Tfun(ft)))
4592-
}
4593-
_ => method.type_,
4594-
};
4595-
let flags = method.flags | MethodFlags::SUPPORT_DYNAMIC_TYPE;
4596-
ShallowMethod {
4597-
type_,
4598-
flags,
4599-
..method
4600-
}
4601-
} else {
4602-
method
4603-
};
4614+
let method = self.possibly_make_supportdyn_method(&class_attributes, method);
46044615
if is_static {
46054616
static_methods.push(method);
46064617
} else {
@@ -4950,7 +4961,7 @@ impl<'o, 't> FlattenSmartConstructors for DirectDeclSmartConstructors<'o, 't> {
49504961
flags.set(MethodFlags::PHP_STD_LIB, attributes.php_std_lib);
49514962
flags.set(
49524963
MethodFlags::SUPPORT_DYNAMIC_TYPE,
4953-
!is_constructor && attributes.support_dynamic_type,
4964+
attributes.support_dynamic_type,
49544965
);
49554966
flags.set(MethodFlags::NO_AUTO_LIKES, attributes.no_auto_likes);
49564967
flags.set(MethodFlags::NEEDS_CONCRETE, attributes.needs_concrete);

hphp/hack/src/hackrs/folded_decl_provider/fold.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ impl<'a, R: Reason> DeclFolder<'a, R> {
472472
is_lateinit: false,
473473
is_dynamicallycallable: false,
474474
is_readonly_prop_or_needs_concrete: false,
475-
supports_dynamic_type: false,
475+
supports_dynamic_type: meth_flags.supports_dynamic_type(),
476476
needs_init: false,
477477
safe_global_variable: false,
478478
no_auto_likes: false,

hphp/hack/src/typing/typing.ml

Lines changed: 22 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -2600,7 +2600,6 @@ module rec Expr : sig
26002600
as in `<<MyAttr()>> class C` (note that attributes are classes) *)
26012601
val new_object :
26022602
expected:ExpectedTy.t option ->
2603-
check_parent:bool ->
26042603
check_not_abstract:bool ->
26052604
is_using_clause:bool ->
26062605
is_attribute:bool ->
@@ -4602,7 +4601,6 @@ end = struct
46024601
new_object
46034602
~expected
46044603
~is_using_clause:ctxt.Context.is_using_clause
4605-
~check_parent:false
46064604
~check_not_abstract:true
46074605
~is_attribute:false
46084606
pos
@@ -5240,7 +5238,6 @@ end = struct
52405238

52415239
and new_object
52425240
~(expected : ExpectedTy.t option)
5243-
~check_parent
52445241
~check_not_abstract
52455242
~is_using_clause
52465243
~is_attribute
@@ -5320,8 +5317,7 @@ end = struct
53205317
end
53215318
in
53225319
if
5323-
(not check_parent)
5324-
&& (not is_using_clause)
5320+
(not is_using_clause)
53255321
&& Typing_disposable.is_disposable_class env class_info
53265322
then
53275323
Typing_error_utils.add_typing_error
@@ -5357,8 +5353,6 @@ end = struct
53575353
let (env, cid_ty) = Env.expand_type env cid_ty in
53585354
if is_generic cid_ty then
53595355
(env, cid_ty)
5360-
else if check_parent then
5361-
(env, c_ty)
53625356
else
53635357
ExprDepTy.make env ~cid:cid_ c_ty
53645358
in
@@ -5494,8 +5488,6 @@ end = struct
54945488
in
54955489
if is_generic stripped_cid_ty then
54965490
(env, cid_ty)
5497-
else if check_parent then
5498-
(env, ty)
54995491
else
55005492
ExprDepTy.make env ~cid:cid_ ty
55015493
in
@@ -5593,85 +5585,23 @@ end = struct
55935585

55945586
(env, (te, ty))
55955587

5596-
and check_parent_construct pos recv_pos env el unpacked_element env_parent =
5597-
let check_not_abstract = false in
5598-
let ((env, ty_err_opt), env_parent) =
5599-
Phase.localize_no_subst env ~ignore_errors:true env_parent
5600-
in
5601-
Option.iter ~f:(Typing_error_utils.add_typing_error ~env) ty_err_opt;
5602-
let ( env,
5603-
_tcid,
5604-
_tal,
5605-
tel,
5606-
typed_unpack_element,
5607-
parent,
5608-
fty,
5609-
should_forget_fakes ) =
5610-
new_object
5611-
~expected:None
5612-
~check_parent:true
5613-
~check_not_abstract
5614-
~is_using_clause:false
5615-
~is_attribute:false
5616-
pos
5617-
env
5618-
((), recv_pos, CIparent)
5619-
[]
5620-
el
5621-
unpacked_element
5622-
in
5623-
(* Not sure why we need to equate these types *)
5624-
let (env, e1) =
5625-
Type.sub_type
5626-
pos
5627-
Reason.URnone
5628-
env
5629-
env_parent
5630-
parent
5631-
Typing_error.Callback.unify_error
5632-
in
5633-
let (env, e2) =
5634-
Type.sub_type
5635-
pos
5636-
Reason.URnone
5637-
env
5638-
parent
5639-
env_parent
5640-
Typing_error.Callback.unify_error
5641-
in
5642-
Option.(
5643-
iter ~f:(Typing_error_utils.add_typing_error ~env)
5644-
@@ merge e1 e2 ~f:Typing_error.both);
5645-
( env,
5646-
tel,
5647-
typed_unpack_element,
5648-
MakeType.void (Reason.witness pos),
5649-
parent,
5650-
fty,
5651-
should_forget_fakes )
5652-
5653-
and call_parent_construct pos recv_pos env el unpacked_element =
5588+
(* Check that the parent exists in a parent::__construct call *)
5589+
and check_parent_construct_receiver pos env =
56545590
match Env.get_parent_ty env with
5655-
| Some parent ->
5656-
check_parent_construct pos recv_pos env el unpacked_element parent
5591+
(* Direct parent, so ok *)
5592+
| Some _parent -> ()
56575593
| None ->
5658-
(* continue here *)
5659-
let (env, ty) = Env.fresh_type_error env pos in
5660-
let should_invalidate_fake_members = true in
5661-
let default =
5662-
(env, [], None, ty, ty, ty, should_invalidate_fake_members)
5663-
in
56645594
(match Env.get_self_id env with
56655595
| Some self ->
56665596
let open TraitMostConcreteParent in
56675597
(match Env.get_class env self with
5598+
(* Self is a trait, so look at trait's parents *)
56685599
| Decl_entry.Found trait when Ast_defs.is_c_trait (Cls.kind trait) ->
56695600
(match trait_most_concrete_parent trait env with
56705601
| None ->
56715602
Typing_error_utils.add_typing_error
56725603
~env
5673-
Typing_error.(primary @@ Primary.Parent_in_trait pos);
5674-
default
5604+
Typing_error.(primary @@ Primary.Parent_in_trait pos)
56755605
| Some NotFound ->
56765606
let trait_reqs =
56775607
Some
@@ -5683,9 +5613,8 @@ end = struct
56835613
Typing_error_utils.add_typing_error
56845614
~env
56855615
Typing_error.(
5686-
primary @@ Primary.Parent_undefined { pos; trait_reqs });
5687-
default
5688-
| Some (Found (c, parent_ty)) ->
5616+
primary @@ Primary.Parent_undefined { pos; trait_reqs })
5617+
| Some (Found (c, _parent_ty)) ->
56895618
(match Typing_env.get_construct env c with
56905619
| (_, Inconsistent) ->
56915620
Typing_error_utils.add_typing_error
@@ -5694,29 +5623,13 @@ end = struct
56945623
primary
56955624
@@ Primary.Trait_parent_construct_inconsistent
56965625
{ pos; decl_pos = Cls.pos c })
5697-
| _ -> ());
5698-
check_parent_construct
5699-
pos
5700-
recv_pos
5701-
env
5702-
el
5703-
unpacked_element
5704-
parent_ty)
5705-
| Decl_entry.Found _self_tc ->
5706-
Typing_error_utils.add_typing_error
5707-
~env
5708-
Typing_error.(
5709-
primary @@ Primary.Parent_undefined { pos; trait_reqs = None });
5710-
default
5711-
| Decl_entry.NotYetAvailable
5712-
| Decl_entry.DoesNotExist ->
5713-
assert false)
5626+
| _ -> ()))
5627+
| _ -> ())
57145628
| None ->
5629+
(* We're not even inside a class *)
57155630
Typing_error_utils.add_typing_error
57165631
~env
5717-
Typing_error.(primary @@ Primary.Parent_outside_class pos);
5718-
let (env, ty) = Env.fresh_type_error env pos in
5719-
(env, [], None, ty, ty, ty, should_invalidate_fake_members))
5632+
Typing_error.(primary @@ Primary.Parent_outside_class pos))
57205633

57215634
(* Depending on the kind of expression we are dealing with
57225635
* The typing of call is different.
@@ -6257,37 +6170,16 @@ end = struct
62576170
| _ -> (env, res))
62586171
| _ -> dispatch_class_const env class_id method_id
62596172
end
6260-
(* Special function `parent::__construct` *)
6261-
| Class_const ((_, pos, CIparent), ((_, construct) as id))
6262-
when String.equal construct SN.Members.__construct ->
6263-
let ( env,
6264-
tel,
6265-
typed_unpack_element,
6266-
ty,
6267-
pty,
6268-
ctor_fty,
6269-
should_forget_fakes ) =
6270-
call_parent_construct p pos env el unpacked_element
6271-
in
6272-
let (env, te) =
6273-
Typing_helpers.make_simplify_typed_expr
6274-
env
6275-
fpos
6276-
ctor_fty
6277-
(Aast.Class_const ((pty, pos, Aast.CIparent), id))
6278-
in
6279-
let result =
6280-
make_call
6281-
env
6282-
te
6283-
[] (* tal: no type arguments to constructor *)
6284-
tel
6285-
typed_unpack_element
6286-
ty
6287-
in
6288-
(result, should_forget_fakes)
62896173
(* Calling parent / class method *)
6290-
| Class_const (class_id, m) -> dispatch_class_const env class_id m
6174+
| Class_const (class_id, m) ->
6175+
let () =
6176+
match (class_id, m) with
6177+
| ((_, pos, CIparent), (_, construct))
6178+
when String.equal construct SN.Members.__construct ->
6179+
check_parent_construct_receiver pos env
6180+
| _ -> ()
6181+
in
6182+
dispatch_class_const env class_id m
62916183
(* Readonly Expressions do not affect the type, but need to be threaded through when they're part of a call *)
62926184
| ReadonlyExpr r ->
62936185
let env = Env.set_readonly env true in
@@ -11272,7 +11164,6 @@ end = struct
1127211164
let (env, _, _, _, _, _, _, _) =
1127311165
Expr.new_object
1127411166
~expected:None
11275-
~check_parent:false
1127611167
~check_not_abstract:false
1127711168
~is_using_clause:false
1127811169
~is_attribute:true

hphp/hack/src/typing/typing_object_get.ml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ let fold_mismatch_opts opt_errs =
112112

113113
let get_member ~is_method env class_info member_name =
114114
if is_method then
115-
Env.get_method env class_info member_name
115+
if String.equal member_name SN.Members.__construct then
116+
fst (Env.get_construct env class_info)
117+
else
118+
Env.get_method env class_info member_name
116119
else
117120
Env.get_prop env class_info member_name
118121

@@ -901,17 +904,6 @@ and obj_get_concrete_class_without_member_info
901904
(mk (Reason.none, Tfun ft), []),
902905
dflt_lval_mismatch,
903906
dflt_rval_mismatch )
904-
else if String.equal id_str SN.Members.__construct then
905-
(* __construct is not an instance method and shouldn't be invoked directly
906-
Note that we already raise a NAST check error in `illegal_name_check` but
907-
we raise a related typing error here to properly keep track of failure.
908-
We prefer a specific error here since the generic 4053 `MemberNotFound`
909-
error, below, would be quite confusing telling us there is no instance
910-
method `__construct` *)
911-
let ty_err =
912-
Typing_error.(primary @@ Primary.Construct_not_instance_method id_pos)
913-
in
914-
default (Some ty_err)
915907
else
916908
let ty_err =
917909
member_not_found

0 commit comments

Comments
 (0)