Skip to content

Commit 1559587

Browse files
Scott Owensmeta-codesync[bot]
authored andcommitted
Add an __ImplementedBy attribute for methods on primitives
Summary: For methods on primitives, we want to give a class in an .hhi, and tie that to the top-level function that is the runtime implementation of the method. We also want to ensure that the types are compatible. We add an attribute (only allowed in .hhi files) that declares the name of the top-level function. We then fund the function's decl and check that it is a subtype of the method's type. The methods type is instantiated as for `meth_caller` to take into account generics from the class. For example, in the .hhi for string, we might have: ``` class string { <<__ImplementedBy('\my_length')>> public method length(): int; } function my_length(string $s): int; ``` We use the `Fun_id.synth` and `Meth_caller.synth` functions from `typing.ml` to build the types of the functions. In particular, the Meth_caller one adds the "this" parameter for us, and moves class generics to function generics. It also tries to add `readonly` and `FunctionRef` wrappings, which we disable for this purpose. Reviewed By: mheiber Differential Revision: D88174803 fbshipit-source-id: 5308745bf57a91e71b94fa4eeaac54b7adc64fa1
1 parent 483e1b5 commit 1559587

37 files changed

Lines changed: 290 additions & 9 deletions

hphp/hack/src/errors/error_codes.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ module Naming = struct
169169
| ToplevelStatement [@value 2128]
170170
| InvalidTypeAccessInWhere [@value 2129]
171171
| AttributeOutsideAllowedFiles [@value 2130]
172+
| HhiAttributeOutsideHhi [@value 2131]
172173
(* Add new Naming codes here! Comment out when deprecating. *)
173174
[@@deriving enum, show { with_path = false }]
174175

hphp/hack/src/hackc/compile/compile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ fn emit_fatal_nast_check_error(err: &NastCheckError) -> Result<Unit, Error> {
568568
NastCheckError::MissingAssign(_) => todo!(),
569569
NastCheckError::CloneReturnType(_) => todo!(),
570570
NastCheckError::PackageExprInInvariant(_) => todo!(),
571+
NastCheckError::AttributeImplementedByOnlyInHhi(_) => todo!(),
571572
}
572573
}
573574

hphp/hack/src/naming/naming_special_names.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ module UserAttributes = struct
402402

403403
let uaAsioLowPri = "__AsioLowPri"
404404

405+
let uaImplementedBy = "__ImplementedBy"
406+
405407
type attr_info = {
406408
contexts: string list;
407409
doc: string;
@@ -862,6 +864,13 @@ module UserAttributes = struct
862864
doc =
863865
"Marks the function as low priority. Will suspend eager execution into a low priority awaitable.";
864866
} );
867+
( uaImplementedBy,
868+
{
869+
contexts = [mthd];
870+
autocomplete = true;
871+
doc =
872+
"Specifies that this method is implemented by a specific top-level function. Only valid in .hhi files.";
873+
} );
865874
])
866875

867876
(* These are names which are allowed in the systemlib but not in normal programs *)

hphp/hack/src/oxidized/gen/error_codes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<3f899c82f15b01a6cf584bae0dd43582>>
6+
// @generated SignedSource<<69f97f6775910fe2cb4cdcd55e935bbd>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -155,6 +155,7 @@ pub enum Naming {
155155
ToplevelStatement = 2128,
156156
InvalidTypeAccessInWhere = 2129,
157157
AttributeOutsideAllowedFiles = 2130,
158+
HhiAttributeOutsideHhi = 2131,
158159
}
159160
impl TrivialDrop for Naming {}
160161
arena_deserializer::impl_deserialize_in_arena!(Naming);

hphp/hack/src/oxidized/gen/nast_check_error.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<2564942d4c07d50efe5d36d05e246ed7>>
6+
// @generated SignedSource<<4073f83a7583567cc25e4fd4e5f52da3>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -276,6 +276,8 @@ pub enum NastCheckError {
276276
},
277277
#[rust_to_ocaml(name = "Attribute_no_auto_dynamic")]
278278
AttributeNoAutoDynamic(pos::Pos),
279+
#[rust_to_ocaml(name = "Attribute_implemented_by_only_in_hhi")]
280+
AttributeImplementedByOnlyInHhi(pos::Pos),
279281
#[rust_to_ocaml(name = "Generic_at_runtime")]
280282
GenericAtRuntime {
281283
pos: pos::Pos,

hphp/hack/src/typing/dune

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@
212212
(modules
213213
nastInitCheck
214214
type_parameter_env_ops
215+
typing
215216
typing_alias
216217
typing_ast_print
217218
typing_argument
@@ -225,6 +226,7 @@
225226
typing_exts
226227
typing_func_terminality
227228
typing_generic_rules
229+
typing_implemented_by
228230
typing_lenv
229231
typing_local_ops
230232
typing_named_params
@@ -247,6 +249,7 @@
247249
typing_dynamic_utils
248250
typing_enforceability
249251
typing_enum
252+
typing_extract_method
250253
typing_intersection
251254
typing_generic_constraint
252255
typing_helpers
@@ -266,6 +269,7 @@
266269
typing_structure
267270
typing_taccess
268271
typing_type_member
272+
typing_type_wellformedness
269273
typing_union
270274
type_validator
271275
typing_const_reifiable
@@ -314,9 +318,7 @@
314318
(name typing_toplevel)
315319
(wrapped false)
316320
(modules
317-
typing
318321
typing_class
319-
typing_extract_method
320322
typing_memoize
321323
typing_toplevel
322324
typing_typedef
@@ -335,8 +337,7 @@
335337
trait_reuse_check
336338
typing_extends
337339
typing_requirements
338-
typing_subtype_method
339-
typing_type_wellformedness)
340+
typing_subtype_method)
340341
(libraries
341342
typing
342343
typing_modules

hphp/hack/src/typing/nast_check/attribute_nast_checks.ml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,38 @@ let check_no_auto_dynamic env attrs =
139139
Nast_check_error.(to_user_error @@ Attribute_no_auto_dynamic pos)
140140
| _ -> ()
141141

142+
let check_implemented_by attrs =
143+
let attr = Naming_attributes.find SN.UserAttributes.uaImplementedBy attrs in
144+
match attr with
145+
| Some { ua_name = (pos, name); ua_params } ->
146+
(* Check file extension *)
147+
let filename = Pos.filename pos in
148+
let path_str = Relative_path.suffix filename in
149+
let is_hhi = Filename.check_suffix path_str ".hhi" in
150+
if not is_hhi then
151+
Errors.add_error
152+
Nast_check_error.(
153+
to_user_error @@ Attribute_implemented_by_only_in_hhi pos);
154+
(* Check parameter count and type *)
155+
(match ua_params with
156+
| [] ->
157+
Errors.add_error
158+
Nast_check_error.(
159+
to_user_error
160+
@@ Attribute_too_few_arguments { pos; name; expected = 1 })
161+
| [(_, _, String _)] -> () (* Valid: one string parameter *)
162+
| [(_, p, _)] ->
163+
Errors.add_error
164+
Nast_check_error.(
165+
to_user_error
166+
@@ Attribute_param_type { pos = p; x = "a string literal" })
167+
| _ ->
168+
Errors.add_error
169+
Nast_check_error.(
170+
to_user_error
171+
@@ Attribute_too_many_arguments { pos; name; expected = 1 }))
172+
| _ -> ()
173+
142174
let check_dynamically_referenced attrs =
143175
let attr =
144176
Naming_attributes.find SN.UserAttributes.uaDynamicallyReferenced attrs
@@ -342,6 +374,7 @@ let handler =
342374
check_autocomplete_valid_text m.m_user_attributes;
343375
check_duplicate_memoize m.m_user_attributes;
344376
check_no_auto_dynamic env m.m_user_attributes;
377+
check_implemented_by m.m_user_attributes;
345378
check_no_sealed_on_constructors m;
346379
check_no_sealed_on_private_methods m;
347380
check_no_sealed_on_interface_methods env m;

hphp/hack/src/typing/nast_check/nast_check_error.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ type t =
167167
x: string;
168168
}
169169
| Attribute_no_auto_dynamic of Pos.t
170+
| Attribute_implemented_by_only_in_hhi of Pos.t
170171
| Generic_at_runtime of {
171172
pos: Pos.t;
172173
prefix: string;
@@ -761,6 +762,12 @@ let attribute_no_auto_dynamic pos =
761762
(pos, "This attribute is not yet supported in user code")
762763
[]
763764

765+
let attribute_implemented_by_only_in_hhi pos =
766+
User_error.make_err
767+
Error_codes.Naming.(to_enum HhiAttributeOutsideHhi)
768+
(pos, "This attribute can only be used in .hhi files")
769+
[]
770+
764771
let generic_at_runtime p prefix =
765772
User_error.make_err
766773
Error_codes.Typing.(to_enum ErasedGenericAtRuntime)
@@ -926,6 +933,8 @@ let to_user_error t =
926933
attribute_not_exact_number_of_args pos name expected actual
927934
| Attribute_param_type { pos; x } -> attribute_param_type pos x
928935
| Attribute_no_auto_dynamic pos -> attribute_no_auto_dynamic pos
936+
| Attribute_implemented_by_only_in_hhi pos ->
937+
attribute_implemented_by_only_in_hhi pos
929938
| Generic_at_runtime { pos; prefix } -> generic_at_runtime pos prefix
930939
| Generics_not_allowed pos -> generics_not_allowed pos
931940
| Local_variable_modified_and_used { pos; pos_useds } ->

hphp/hack/src/typing/nast_check/nast_check_error.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ type t =
166166
x: string;
167167
}
168168
| Attribute_no_auto_dynamic of Pos.t
169+
| Attribute_implemented_by_only_in_hhi of Pos.t
169170
| Generic_at_runtime of {
170171
pos: Pos.t;
171172
prefix: string;

hphp/hack/src/typing/typing.ml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8931,6 +8931,7 @@ end
89318931

89328932
and Method_caller : sig
89338933
val synth :
8934+
?for_meth_caller:bool ->
89348935
Pos.t ->
89358936
Aast_defs.class_name * Ast_defs.pstring ->
89368937
env ->
@@ -9047,7 +9048,7 @@ end = struct
90479048
reason;
90489049
}))
90499050

9050-
let synth pos (class_name, method_name) env =
9051+
let synth ?(for_meth_caller = true) pos (class_name, method_name) env =
90519052
match Env.get_class env (snd class_name) with
90529053
| Decl_entry.NotYetAvailable
90539054
| Decl_entry.DoesNotExist ->
@@ -9156,8 +9157,18 @@ end = struct
91569157
(env, mk (get_reason ty, Tfun ft))
91579158
| _ -> (env, ty))
91589159
in
9159-
let (env, ty) = set_capture_only_readonly env ty in
9160-
let ty = make_function_ref ~contains_generics:false env pos ty in
9160+
let (env, ty) =
9161+
if for_meth_caller then
9162+
set_capture_only_readonly env ty
9163+
else
9164+
(env, ty)
9165+
in
9166+
let ty =
9167+
if for_meth_caller then
9168+
make_function_ref ~contains_generics:false env pos ty
9169+
else
9170+
ty
9171+
in
91619172
let expr = Method_caller (class_name, method_name) in
91629173
make_result env pos expr ty
91639174
| _ -> failwith "Expected a function type")

0 commit comments

Comments
 (0)