diff --git a/CHANGELOG.md b/CHANGELOG.md index 997f809d328..3a32f6e7a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ #### :house: Internal +- Keep resolved FFI specifications out of the current parsetree: `pval_prim` is the primitive string as written, and the type checker resolves FFI externals, with current-AST and CMT format bumps. https://github.com/rescript-lang/rescript/pull/8728 - Make `dict` an abstract type instead of a record with a hidden field. https://github.com/rescript-lang/rescript/pull/8752 - Represent dict patterns with a dedicated `Tpat_dict` typed tree node. https://github.com/rescript-lang/rescript/pull/8751 - Represent dict patterns with a dedicated `Ppat_dict` node. https://github.com/rescript-lang/rescript/pull/8749 diff --git a/compiler/core/lam_compile_external_call.ml b/compiler/core/lam_compile_external_call.ml index c6a3417b37b..025ba0f86fd 100644 --- a/compiler/core/lam_compile_external_call.ml +++ b/compiler/core/lam_compile_external_call.ml @@ -298,7 +298,7 @@ let translate_ffi ?(transformed_jsx = false) (cxt : Lam_compile_context.t) E.new_ fn args) | ( (Decl_send _ | Decl_get _ | Decl_set _ | Decl_get_index | Decl_set_index), Some Module_itself ) -> - assert false (* rejected during digestion *) + assert false (* rejected during resolution *) | Decl_val {name}, _ when decl.effective_arity = 0 -> (* a global value *) let e = translate_scoped_module_val named_module name scopes in diff --git a/compiler/ext/config.ml b/compiler/ext/config.ml index 8f503e1f170..eda706ae43b 100644 --- a/compiler/ext/config.ml +++ b/compiler/ext/config.ml @@ -2,9 +2,9 @@ let cmi_magic_number = "Caml1999I035" (* Magic numbers for marshaled values of the *current* parsetree, whose layout changes across compiler versions. *) -and ast_impl_magic_number = "ResImpl01315" +and ast_impl_magic_number = "ResImpl01316" -and ast_intf_magic_number = "ResIntf01315" +and ast_intf_magic_number = "ResIntf01316" (* Magic numbers of the frozen Parsetree0 (OCaml 4.06) layout used on the external-PPX wire. They must never be written in front of a @@ -13,6 +13,6 @@ and ast0_impl_magic_number = "Caml1999M022" and ast0_intf_magic_number = "Caml1999N022" -and cmt_magic_number = "Caml1999T041" +and cmt_magic_number = "Caml1999T042" let load_path = ref ([] : string list) diff --git a/compiler/frontend/ast_attributes.ml b/compiler/frontend/ast_attributes.ml index 38cd94f5150..e3b28a7cdaf 100644 --- a/compiler/frontend/ast_attributes.ml +++ b/compiler/frontend/ast_attributes.ml @@ -69,14 +69,13 @@ let prim_to_be_encoded (name : string) = not (first_char_special name) They are not considered externals, they are part of the language *) -let rs_externals (attrs : t) (pval_prim : Parsetree.primitive_repr option) = +let is_ffi_external (attrs : t) (pval_prim : string Asttypes.loc option) = match (attrs, pval_prim) with - | _, (None | Some (Prim_ffi _ | Prim_inline_const _)) -> false - (* [None] is a [val]; an already-digested external is not processed again *) - | [], Some (Prim_name name) -> + | _, None -> false (* [None] is a [val] *) + | [], Some {txt = name} -> (* Not any attribute found *) prim_to_be_encoded name - | _, Some (Prim_name name) -> + | _, Some {txt = name} -> Ext_list.exists_fst attrs (fun ({txt} : string Asttypes.loc) -> external_attrs |> Array.exists (fun (x : string) -> txt = x)) || prim_to_be_encoded name diff --git a/compiler/frontend/ast_attributes.mli b/compiler/frontend/ast_attributes.mli index 16f7e2f9075..1536e4c536a 100644 --- a/compiler/frontend/ast_attributes.mli +++ b/compiler/frontend/ast_attributes.mli @@ -50,7 +50,7 @@ val process_derive_type : t -> derive_attr * t val internal_expansive : attr -val rs_externals : t -> Parsetree.primitive_repr option -> bool +val is_ffi_external : t -> string Asttypes.loc option -> bool val is_gentype : attr -> bool diff --git a/compiler/frontend/ast_exp_handle_external.ml b/compiler/frontend/ast_exp_handle_external.ml index 4ab1212186f..76c2af05524 100644 --- a/compiler/frontend/ast_exp_handle_external.ml +++ b/compiler/frontend/ast_exp_handle_external.ml @@ -25,7 +25,7 @@ let handle_debugger loc (payload : Ast_payload.t) = match payload with | PStr [] -> - Ast_external_mk.local_external_apply loc ~pval_prim:(Prim_name "%debugger") + Ast_external_mk.local_external_apply loc ~pval_prim:"%debugger" ~pval_type: (Ast_helper.Typ.arrow [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] @@ -45,8 +45,7 @@ let handle_raw loc payload = { exp with pexp_desc = - Ast_external_mk.local_external_apply loc - ~pval_prim:(Prim_name "#raw_expr") + Ast_external_mk.local_external_apply loc ~pval_prim:"#raw_expr" ~pval_type: (Ast_helper.Typ.arrow [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] @@ -91,8 +90,7 @@ let handle_ffi ~loc ~payload = { exp with pexp_desc = - Ast_external_mk.local_external_apply loc - ~pval_prim:(Prim_name "#raw_expr") + Ast_external_mk.local_external_apply loc ~pval_prim:"#raw_expr" ~pval_type: (Ast_helper.Typ.arrow [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] @@ -111,8 +109,7 @@ let handle_raw_structure loc payload = { exp with pexp_desc = - Ast_external_mk.local_external_apply loc - ~pval_prim:(Prim_name "#raw_stmt") + Ast_external_mk.local_external_apply loc ~pval_prim:"#raw_stmt" ~pval_type: (Ast_helper.Typ.arrow [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] diff --git a/compiler/frontend/ast_external.ml b/compiler/frontend/ast_external.ml index e2c121866ad..15b5822e5e1 100644 --- a/compiler/frontend/ast_external.ml +++ b/compiler/frontend/ast_external.ml @@ -22,77 +22,105 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -let handle_external_in_sig (self : Ast_mapper.mapper) - (prim : Parsetree.value_description) (sigi : Parsetree.signature_item) : - Parsetree.signature_item = +(* Resolve an FFI external that [validate] accepted. That cannot fail, and + its warnings were reported then, so they are not repeated. *) +let resolve_validated (value_desc : Parsetree.value_description) + (prim_name : string) = + Warnings.without_warnings (fun () -> + Ast_external_process.resolve value_desc.pval_loc value_desc.pval_type + value_desc.pval_attributes prim_name) + +let resolve (value_desc : Parsetree.value_description) (prim_name : string) : + Primitive.resolved_external = + if prim_name = Ast_external_mk.inline_const_prim then + match Ast_external_mk.inline_const_of_declaration value_desc with + | Some c -> + { + resolved_type = value_desc.pval_type; + resolved_attributes = []; + resolved_name = ""; + resolved_kind = Kind_inline_const c; + } + | None -> + Location.raise_errorf ~loc:value_desc.pval_loc + "\"%s\" is reserved for %@inline constants" prim_name + else if + Ast_attributes.is_ffi_external value_desc.pval_attributes + value_desc.pval_prim + then + let {Ast_external_process.pval_type; spec; pval_attributes} = + resolve_validated value_desc prim_name + in + { + resolved_type = pval_type; + resolved_attributes = pval_attributes; + resolved_name = prim_name; + resolved_kind = Kind_external spec; + } + else + { + resolved_type = value_desc.pval_type; + resolved_attributes = value_desc.pval_attributes; + resolved_name = prim_name; + resolved_kind = Kind_intrinsic; + } + +(* The built-in PPX, which every compilation runs before type checking, links + this module, so the type checker always finds the resolver registered. *) +let () = Primitive.resolve_external := resolve + +let resolved_val (value_desc : Parsetree.value_description) + ({pval_type; pval_attributes} : Ast_external_process.resolution) : + Parsetree.value_description = + {value_desc with pval_type; pval_prim = None; pval_attributes} + +let resolved_ffi_external (value_desc : Parsetree.value_description) = + match value_desc.pval_prim with + | None -> value_desc + | Some {txt = prim_name} -> + resolved_val value_desc (resolve_validated value_desc prim_name) + +(* Resolve an FFI external now to report its errors and warnings, and keep it + as written for the type checker, which resolves it again through + [resolve]. Returns the external as written, and its resolved form as a + [val] when a relative [@module] makes it unfit for cross-module inlining. *) +let validate (self : Ast_mapper.mapper) (prim : Parsetree.value_description) = let loc = prim.pval_loc in let pval_type = self.typ self prim.pval_type in let pval_attributes = self.attributes self prim.pval_attributes in match prim.pval_prim with | None -> Location.raise_errorf ~loc "empty primitive string" - | Some (Prim_ffi _ | Prim_inline_const _) -> - Location.raise_errorf ~loc "external declaration already processed" - | Some (Prim_name v) -> ( - match - Ast_external_process.handle_attributes_as_prim loc pval_type - pval_attributes v - with - | {pval_type; pval_prim; pval_attributes; no_inline_cross_module} -> - { - sigi with - psig_desc = - Psig_value - { - prim with - pval_type; - pval_prim = - (if no_inline_cross_module then None else Some pval_prim); - pval_attributes; - }; - }) + | Some {txt = prim_name} -> + let resolution = + Ast_external_process.resolve loc pval_type pval_attributes prim_name + in + ( {prim with pval_type; pval_attributes}, + if resolution.no_inline_cross_module then + Some (resolved_val prim resolution) + else None ) + +let handle_external_in_sig (self : Ast_mapper.mapper) + (prim : Parsetree.value_description) (sigi : Parsetree.signature_item) : + Parsetree.signature_item = + let external_, not_inlined = validate self prim in + { + sigi with + psig_desc = Psig_value (Option.value not_inlined ~default:external_); + } let handle_external_in_stru (self : Ast_mapper.mapper) (prim : Parsetree.value_description) (str : Parsetree.structure_item) : Parsetree.structure_item = - let loc = prim.pval_loc in - let pval_type = self.typ self prim.pval_type in - let pval_attributes = self.attributes self prim.pval_attributes in - match prim.pval_prim with - | None -> Location.raise_errorf ~loc "empty primitive string" - | Some (Prim_ffi _ | Prim_inline_const _) -> - Location.raise_errorf ~loc "external declaration already processed" - | Some (Prim_name v) -> ( - match - Ast_external_process.handle_attributes_as_prim loc pval_type - pval_attributes v - with - | {pval_type; pval_prim; pval_attributes; no_inline_cross_module} -> - let external_result = - { - str with - pstr_desc = - Pstr_primitive - {prim with pval_type; pval_prim = Some pval_prim; pval_attributes}; - } - in - if not no_inline_cross_module then external_result - else - let open Ast_helper in - Str.include_ ~loc - (Incl.mk ~loc - (Mod.constraint_ ~loc - (Mod.structure ~loc [external_result]) - (Mty.signature ~loc - [ - { - psig_desc = - Psig_value - { - prim with - pval_type; - pval_prim = None; - pval_attributes; - }; - psig_loc = loc; - }; - ])))) + let external_, not_inlined = validate self prim in + let external_result = {str with pstr_desc = Pstr_primitive external_} in + match not_inlined with + | None -> external_result + | Some resolved_val -> + let loc = prim.pval_loc in + let open Ast_helper in + Str.include_ ~loc + (Incl.mk ~loc + (Mod.constraint_ ~loc + (Mod.structure ~loc [external_result]) + (Mty.signature ~loc + [{psig_desc = Psig_value resolved_val; psig_loc = loc}]))) diff --git a/compiler/frontend/ast_external.mli b/compiler/frontend/ast_external.mli index 140c453037f..6b562822a4e 100644 --- a/compiler/frontend/ast_external.mli +++ b/compiler/frontend/ast_external.mli @@ -22,6 +22,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) +val resolve : + Parsetree.value_description -> string -> Primitive.resolved_external +(** The resolver behind [Primitive.resolve_external]: an [@inline] constant, + an FFI external with its specification, or an intrinsic. *) + +val resolved_ffi_external : + Parsetree.value_description -> Parsetree.value_description +(** An FFI external that [handle_external_in_sig] or [handle_external_in_stru] + accepted, as a [val] of its resolved type and remaining attributes *) + val handle_external_in_sig : Ast_mapper.mapper -> Parsetree.value_description -> diff --git a/compiler/frontend/ast_external_mk.ml b/compiler/frontend/ast_external_mk.ml index 6614ae86986..3e086e2ea42 100644 --- a/compiler/frontend/ast_external_mk.ml +++ b/compiler/frontend/ast_external_mk.ml @@ -22,10 +22,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -let local_external_apply loc ?(pval_attributes = []) - ~(pval_prim : Parsetree.primitive_repr) ~(pval_type : Parsetree.core_type) - ?(local_module_name = "J") ?(local_fun_name = "unsafe_expr") - (args : Parsetree.expression list) : Parsetree.expression_desc = +let local_external_apply loc ?(pval_attributes = []) ~(pval_prim : string) + ~(pval_type : Parsetree.core_type) ?(local_module_name = "J") + ?(local_fun_name = "unsafe_expr") (args : Parsetree.expression list) : + Parsetree.expression_desc = Pexp_letmodule ( {txt = local_module_name; loc}, Ast_helper.Mod.structure ~loc @@ -35,7 +35,7 @@ let local_external_apply loc ?(pval_attributes = []) pval_name = {txt = local_fun_name; loc}; pval_type; pval_loc = loc; - pval_prim = Some pval_prim; + pval_prim = Some {txt = pval_prim; loc}; pval_attributes; }; ], @@ -44,18 +44,41 @@ let local_external_apply loc ?(pval_attributes = []) {txt = Ldot (Lident local_module_name, local_fun_name); loc}) (Ext_list.map args (fun x -> (Asttypes.Nolabel, x))) ) -let inline_const (c : External_ffi_types.inline_const) : - Parsetree.primitive_repr = - Prim_inline_const c +let inline_const_prim = "#rescript-inline" -let inline_string semantic = inline_const (Const_string semantic) +let inline_const_of_expression (expression : Parsetree.expression) : + External_ffi_types.inline_const option = + match (Ast_payload.unwrap_braces expression).pexp_desc with + | Pexp_constant (Pconst_string _) + | Pexp_template {source_segments = [_]; values = []} -> ( + match Ast_payload.semantic_string_of_expression expression with + | Some semantic -> Some (Const_string semantic) + | None -> assert false) + | Pexp_constant (Pconst_integer (s, None)) -> + Some (Const_int (Int32.of_string s)) + | Pexp_constant (Pconst_integer (s, Some 'n')) -> + let positive, digits = Bigint_utils.parse_bigint s in + Some (Const_bigint {positive; digits}) + | Pexp_constant (Pconst_float (s, None)) -> Some (Const_float s) + | Pexp_construct ({txt = Lident (("true" | "false") as txt)}, {txt = []}) -> + Some (Const_bool (txt = "true")) + | _ -> None -let inline_bool b = inline_const (Const_bool b) +let inline_const_declaration ~(attr_loc : Location.t) + (value_desc : Parsetree.value_description) + (expression : Parsetree.expression) : Parsetree.value_description = + { + value_desc with + pval_prim = Some {txt = inline_const_prim; loc = attr_loc}; + pval_attributes = + [ + ( {txt = "inline"; loc = attr_loc}, + PStr [Ast_helper.Str.eval ~loc:expression.pexp_loc expression] ); + ]; + } -let inline_int i = inline_const (Const_int i) - -let inline_bigint s = - let positive, digits = Bigint_utils.parse_bigint s in - inline_const (Const_bigint {positive; digits}) - -let inline_float s = inline_const (Const_float s) +let inline_const_of_declaration (value_desc : Parsetree.value_description) = + match value_desc.pval_attributes with + | [({txt = "inline"}, PStr [{pstr_desc = Pstr_eval (expression, _)}])] -> + inline_const_of_expression expression + | _ -> None diff --git a/compiler/frontend/ast_external_mk.mli b/compiler/frontend/ast_external_mk.mli index e92f754bbcd..599285cad43 100644 --- a/compiler/frontend/ast_external_mk.mli +++ b/compiler/frontend/ast_external_mk.mli @@ -25,7 +25,7 @@ val local_external_apply : Location.t -> ?pval_attributes:Parsetree.attributes -> - pval_prim:Parsetree.primitive_repr -> + pval_prim:string -> pval_type:Parsetree.core_type -> ?local_module_name:string -> ?local_fun_name:string -> @@ -42,12 +42,24 @@ val local_external_apply : ]} *) -val inline_string : string -> Parsetree.primitive_repr +val inline_const_prim : string +(** The primitive string of an [@inline] constant's declaration *) -val inline_bool : bool -> Parsetree.primitive_repr +val inline_const_of_expression : + Parsetree.expression -> External_ffi_types.inline_const option +(** The constant an [@inline] literal denotes, if it is one an [@inline] + value can carry *) -val inline_int : int32 -> Parsetree.primitive_repr +val inline_const_declaration : + attr_loc:Location.t -> + Parsetree.value_description -> + Parsetree.expression -> + Parsetree.value_description +(** [inline_const_declaration ~attr_loc value_desc literal] turns [value_desc] + into the [external] the type checker resolves to an inline constant: its + primitive is [inline_const_prim] and its only attribute is + [@inline(literal)]. *) -val inline_bigint : string -> Parsetree.primitive_repr - -val inline_float : string -> Parsetree.primitive_repr +val inline_const_of_declaration : + Parsetree.value_description -> External_ffi_types.inline_const option +(** The constant of a declaration built by [inline_const_declaration] *) diff --git a/compiler/frontend/ast_external_process.ml b/compiler/frontend/ast_external_process.ml index c7e04abbce0..380dc69e54c 100644 --- a/compiler/frontend/ast_external_process.ml +++ b/compiler/frontend/ast_external_process.ml @@ -396,13 +396,6 @@ let check_return_wrapper loc (wrapper : External_ffi_types.return_wrapper) if Ast_core_type.is_user_option result_type then wrapper else Bs_syntaxerr.err loc Expect_opt_in_bs_return_to_opt -type response = { - pval_type: Parsetree.core_type; - pval_prim: Parsetree.primitive_repr; - pval_attributes: Parsetree.attributes; - no_inline_cross_module: bool; -} - let process_obj (loc : Location.t) (st : external_desc) (prim_name : string) (arg_types_ty : Parsetree.arg list) (result_type : Ast_core_type.t) : int * Parsetree.core_type * External_ffi_types.t = @@ -888,10 +881,16 @@ let external_decl_of_non_obj (loc : Location.t) (st : external_desc) | {get_name = Some _} -> Location.raise_errorf ~loc "Attribute found that conflicts with %@get" +type resolution = { + pval_type: Parsetree.core_type; + spec: External_ffi_types.t; + pval_attributes: Parsetree.attributes; + no_inline_cross_module: bool; +} + (** Note that the passed [type_annotation] is already processed by visitor pattern before*) -let handle_attributes (loc : Location.t) (type_annotation : Parsetree.core_type) - (prim_attributes : Ast_attributes.t) (prim_name : string) : - Parsetree.core_type * External_ffi_types.t * Parsetree.attributes * bool = +let resolve (loc : Location.t) (type_annotation : Parsetree.core_type) + (prim_attributes : Ast_attributes.t) (prim_name : string) : resolution = let prim_name_with_source = {name = prim_name; source = External} in let result_type, arg_types_ty = (* Note this assumes external type is syntatic (no abstraction)*) @@ -907,7 +906,12 @@ let handle_attributes (loc : Location.t) (type_annotation : Parsetree.core_type) let _arity, new_type, spec = process_obj loc external_desc prim_name arg_types_ty result_type in - (new_type, spec, unused_attrs, false) + { + pval_type = new_type; + spec; + pval_attributes = unused_attrs; + no_inline_cross_module = false; + } else let splice = external_desc.splice in let arg_type_specs, args, arg_type_specs_length = @@ -995,37 +999,12 @@ let handle_attributes (loc : Location.t) (type_annotation : Parsetree.core_type) let return_wrapper = check_return_wrapper loc external_desc.return_wrapper result_type in - ( (match args with - | [] -> result_type - | _ -> Ast_helper.Typ.arrow ~loc args result_type), - External_ffi_types.ffi_bs arg_type_specs return_wrapper decl, - unused_attrs, - relative ) - -let handle_attributes_as_prim (pval_loc : Location.t) (typ : Ast_core_type.t) - (attrs : Ast_attributes.t) (prim_name : string) : response = - let pval_type, ffi, pval_attributes, no_inline_cross_module = - handle_attributes pval_loc typ attrs prim_name - in - { - pval_type; - pval_prim = Prim_ffi {name = prim_name; spec = ffi}; - pval_attributes; - no_inline_cross_module; - } - -let pval_prim_of_option_labels (labels : (bool * string Asttypes.loc) list) - (ends_with_unit : bool) = - let arg_kinds = - Ext_list.fold_right labels - (if ends_with_unit then [External_arg_spec.empty_kind Extern_unit] else []) - (fun (is_option, p) arg_kinds -> - let label_name = p.txt in - let obj_arg_label = - if is_option then External_arg_spec.optional false label_name - else External_arg_spec.obj_label label_name - in - {obj_arg_type = Nothing; obj_arg_label} :: arg_kinds) - in - Parsetree.Prim_ffi - {name = ""; spec = External_ffi_types.ffi_obj_create arg_kinds} + { + pval_type = + (match args with + | [] -> result_type + | _ -> Ast_helper.Typ.arrow ~loc args result_type); + spec = External_ffi_types.ffi_bs arg_type_specs return_wrapper decl; + pval_attributes = unused_attrs; + no_inline_cross_module = relative; + } diff --git a/compiler/frontend/ast_external_process.mli b/compiler/frontend/ast_external_process.mli index 7c564d42572..8fd172b1610 100644 --- a/compiler/frontend/ast_external_process.mli +++ b/compiler/frontend/ast_external_process.mli @@ -22,23 +22,19 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -type response = { +type resolution = { pval_type: Parsetree.core_type; - pval_prim: Parsetree.primitive_repr; - pval_attributes: Parsetree.attributes; + (** The declared type, without the arguments FFI attributes erase *) + spec: External_ffi_types.t; + pval_attributes: Parsetree.attributes; (** Attributes not consumed *) no_inline_cross_module: bool; + (** The external names a relative module, so other modules must not + inline it *) } -val handle_attributes_as_prim : - Location.t -> Ast_core_type.t -> Ast_attributes.t -> string -> response -(** - [handle_attributes_as_prim - loc pval_name.txt pval_type pval_attributes pval_prim] - [pval_name.txt] is the name of identifier - [pval_prim] is the name of string literal - - return value is of [pval_type, pval_prim, new_attrs] -*) - -val pval_prim_of_option_labels : - (bool * string Asttypes.loc) list -> bool -> Parsetree.primitive_repr +val resolve : + Location.t -> Ast_core_type.t -> Ast_attributes.t -> string -> resolution +(** [resolve loc pval_type pval_attributes prim_name] applies the FFI + attributes of an [external] declaration. Errors are raised and warnings + reported for the declaration, and the attributes it consumes are marked + used. *) diff --git a/compiler/frontend/bs_ast_invariant.ml b/compiler/frontend/bs_ast_invariant.ml index 89cec0e9a3c..72a8ccbf9c8 100644 --- a/compiler/frontend/bs_ast_invariant.ml +++ b/compiler/frontend/bs_ast_invariant.ml @@ -60,7 +60,7 @@ let check_constant loc (const : Parsetree.constant) = (* Note we only used Bs_ast_iterator here, we can reuse compiler-libs instead of rolling our own*) -let emit_external_warnings : iterator = +let emit_external_warnings ~resolved_ffi_external : iterator = { super with type_declaration = @@ -112,26 +112,18 @@ let emit_external_warnings : iterator = value_description = (fun self v -> match v with - | ({pval_loc; pval_prim = Some (Prim_name byte_name); pval_type} : - Parsetree.value_description) -> ( - match byte_name with - | ("%identity" | "%component_identity") - when not (Ast_core_type.is_arity_one pval_type) -> - Location.raise_errorf ~loc:pval_loc - "%s expects a function type of the form 'a => 'b (arity 1)" - byte_name - | _ -> - if byte_name <> "" then - let c = String.unsafe_get byte_name 0 in - if not (c = '%' || c = '#' || c = '?') then - Location.prerr_warning pval_loc - (Warnings.Bs_ffi_warning - (byte_name ^ " such externals are unsafe")) - else super.value_description self v - else - Location.prerr_warning pval_loc - (Warnings.Bs_ffi_warning - (byte_name ^ " such externals are unsafe"))) + | _ when Ast_attributes.is_ffi_external v.pval_attributes v.pval_prim -> + super.value_description self (resolved_ffi_external v) + | { + pval_loc; + pval_prim = + Some {txt = ("%identity" | "%component_identity") as byte_name}; + pval_type; + } + when not (Ast_core_type.is_arity_one pval_type) -> + Location.raise_errorf ~loc:pval_loc + "%s expects a function type of the form 'a => 'b (arity 1)" + byte_name | _ -> super.value_description self v); pat = (fun self (pat : Parsetree.pattern) -> @@ -160,8 +152,48 @@ let rec iter_warnings_on_sigi (stru : Parsetree.signature) = iter_warnings_on_sigi rest | _ -> ()) -let emit_external_warnings_on_structure (stru : Parsetree.structure) = - emit_external_warnings.structure emit_external_warnings stru +let emit_external_warnings_on_structure ~resolved_ffi_external + (stru : Parsetree.structure) = + let it = emit_external_warnings ~resolved_ffi_external in + it.structure it stru -let emit_external_warnings_on_signature (sigi : Parsetree.signature) = - emit_external_warnings.signature emit_external_warnings sigi +let emit_external_warnings_on_signature ~resolved_ffi_external + (sigi : Parsetree.signature) = + let it = emit_external_warnings ~resolved_ffi_external in + it.signature it sigi + +(* [json] payloads are syntax-level expressions until built-in FFI processing + consumes valid [@as(json`...`)] occurrences. Reject anything left only + after that processing, so generic attributes and ordinary expressions + cannot reinterpret them as strings. *) +let unconsumed_json_iterator ~resolved_ffi_external : iterator = + { + super with + value_description = + (fun self v -> + if Ast_attributes.is_ffi_external v.pval_attributes v.pval_prim then + super.value_description self (resolved_ffi_external v) + else super.value_description self v); + expr = + (fun self expression -> + match expression.pexp_desc with + | Pexp_constant (Pconst_json _) -> + Ast_payload.reject_json_literal ~loc:expression.pexp_loc + | _ -> super.expr self expression); + pat = + (fun self pattern -> + match pattern.ppat_desc with + | Ppat_constant (Pconst_json _) -> + Ast_payload.reject_json_literal ~loc:pattern.ppat_loc + | _ -> super.pat self pattern); + } + +let reject_unconsumed_json_on_structure ~resolved_ffi_external + (stru : Parsetree.structure) = + let it = unconsumed_json_iterator ~resolved_ffi_external in + it.structure it stru + +let reject_unconsumed_json_on_signature ~resolved_ffi_external + (sigi : Parsetree.signature) = + let it = unconsumed_json_iterator ~resolved_ffi_external in + it.signature it sigi diff --git a/compiler/frontend/bs_ast_invariant.mli b/compiler/frontend/bs_ast_invariant.mli index b3b580ebfa5..a6b54ceadff 100644 --- a/compiler/frontend/bs_ast_invariant.mli +++ b/compiler/frontend/bs_ast_invariant.mli @@ -34,6 +34,31 @@ val iter_warnings_on_stru : Parsetree.structure -> unit val iter_warnings_on_sigi : Parsetree.signature -> unit -val emit_external_warnings_on_structure : Parsetree.structure -> unit +(** The checks below see an FFI external in its resolved form, given by + [resolved_ffi_external]: the declaration stays as written until type + checking, so its argument types still carry the attributes that + resolution consumes. *) -val emit_external_warnings_on_signature : Parsetree.signature -> unit +val emit_external_warnings_on_structure : + resolved_ffi_external: + (Parsetree.value_description -> Parsetree.value_description) -> + Parsetree.structure -> + unit + +val emit_external_warnings_on_signature : + resolved_ffi_external: + (Parsetree.value_description -> Parsetree.value_description) -> + Parsetree.signature -> + unit + +val reject_unconsumed_json_on_structure : + resolved_ffi_external: + (Parsetree.value_description -> Parsetree.value_description) -> + Parsetree.structure -> + unit + +val reject_unconsumed_json_on_signature : + resolved_ffi_external: + (Parsetree.value_description -> Parsetree.value_description) -> + Parsetree.signature -> + unit diff --git a/compiler/frontend/bs_builtin_ppx.ml b/compiler/frontend/bs_builtin_ppx.ml index 21af6c88990..06eab94ef1b 100644 --- a/compiler/frontend/bs_builtin_ppx.ml +++ b/compiler/frontend/bs_builtin_ppx.ml @@ -138,8 +138,7 @@ let expr_mapper ~async_context ~in_function_def (self : mapper) let source = "/" ^ pattern ^ "/" ^ flags in Ast_payload.validate_raw_source ~kind:Raw_re ~loc ~offset:0 source; let raw = - Ast_external_mk.local_external_apply loc - ~pval_prim:(Prim_name "#raw_expr") + Ast_external_mk.local_external_apply loc ~pval_prim:"#raw_expr" ~pval_type: (Ast_helper.Typ.arrow [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] @@ -473,81 +472,24 @@ let signature_item_mapper (self : mapper) (sigi : Parsetree.signature_item) : | Psig_type (rf, tdcls) -> Ast_tdcls.handle_tdcls_in_sigi self sigi rf tdcls | Psig_value ({pval_attributes; pval_prim} as value_desc) -> ( let pval_attributes = self.attributes self pval_attributes in - if Ast_attributes.rs_externals pval_attributes pval_prim then + if Ast_attributes.is_ffi_external pval_attributes pval_prim then Ast_external.handle_external_in_sig self value_desc sigi else match Ast_attributes.has_inline_payload pval_attributes with - | Some ((_, PStr [{pstr_desc = Pstr_eval (expression, _)}]) as attr) -> ( - match (Ast_payload.unwrap_braces expression).pexp_desc with - | Pexp_constant (Pconst_string _) - | Pexp_template {source_segments = [_]; values = []} -> - let semantic = - match Ast_payload.semantic_string_of_expression expression with - | Some semantic -> semantic - | None -> assert false - in - succeed attr pval_attributes; - { - sigi with - psig_desc = - Psig_value - { - value_desc with - pval_prim = Some (Ast_external_mk.inline_string semantic); - pval_attributes = []; - }; - } - | Pexp_constant (Pconst_integer (s, None)) -> - succeed attr pval_attributes; - let s = Int32.of_string s in - { - sigi with - psig_desc = - Psig_value - { - value_desc with - pval_prim = Some (Ast_external_mk.inline_int s); - pval_attributes = []; - }; - } - | Pexp_constant (Pconst_integer (s, Some 'n')) -> - succeed attr pval_attributes; - { - sigi with - psig_desc = - Psig_value - { - value_desc with - pval_prim = Some (Ast_external_mk.inline_bigint s); - pval_attributes = []; - }; - } - | Pexp_constant (Pconst_float (s, None)) -> - succeed attr pval_attributes; - { - sigi with - psig_desc = - Psig_value - { - value_desc with - pval_prim = Some (Ast_external_mk.inline_float s); - pval_attributes = []; - }; - } - | Pexp_construct ({txt = Lident (("true" | "false") as txt)}, {txt = []}) - -> + | Some + (({loc = attr_loc}, PStr [{pstr_desc = Pstr_eval (expression, _)}]) as + attr) -> ( + match Ast_external_mk.inline_const_of_expression expression with + | Some _ -> succeed attr pval_attributes; { sigi with psig_desc = Psig_value - { - value_desc with - pval_prim = Some (Ast_external_mk.inline_bool (txt = "true")); - pval_attributes = []; - }; + (Ast_external_mk.inline_const_declaration ~attr_loc value_desc + expression); } - | _ -> default_mapper.signature_item self sigi) + | None -> default_mapper.signature_item self sigi) | Some _ | None -> default_mapper.signature_item self sigi) | _ -> default_mapper.signature_item self sigi @@ -578,7 +520,7 @@ let structure_item_mapper (self : mapper) (str : Parsetree.structure_item) : | Pstr_type (rf, tdcls) (* [ {ptype_attributes} as tdcl ] *) -> Ast_tdcls.handle_tdcls_in_stru self str rf tdcls | Pstr_primitive prim - when Ast_attributes.rs_externals prim.pval_attributes prim.pval_prim -> + when Ast_attributes.is_ffi_external prim.pval_attributes prim.pval_prim -> Ast_external.handle_external_in_stru self prim str | Pstr_value ( Nonrecursive, @@ -599,74 +541,38 @@ let structure_item_mapper (self : mapper) (str : Parsetree.structure_item) : Option.iter (fun (_, payload) -> Ast_payload.reject_json_literal_payload payload) has_inline_property; - match - (has_inline_property, (Ast_payload.unwrap_braces pvb_expr).pexp_desc) - with - | ( Some attr, - ( Pexp_constant (Pconst_string _) - | Pexp_template {source_segments = [_]; values = []} ) ) -> - let semantic = - match Ast_payload.semantic_string_of_expression pvb_expr with - | Some semantic -> semantic - | None -> assert false - in - succeed attr pvb_attributes; - { - str with - pstr_desc = - Pstr_primitive - { - pval_name; - pval_type = Ast_literal.type_string (); - pval_loc = pvb_loc; - pval_attributes = []; - pval_prim = Some (Ast_external_mk.inline_string semantic); - }; - } - | Some attr, Pexp_constant (Pconst_integer (s, None)) -> - let s = Int32.of_string s in - succeed attr pvb_attributes; - { - str with - pstr_desc = - Pstr_primitive - { - pval_name; - pval_type = Ast_literal.type_int (); - pval_loc = pvb_loc; - pval_attributes = []; - pval_prim = Some (Ast_external_mk.inline_int s); - }; - } - | Some attr, Pexp_constant (Pconst_float (s, None)) -> - succeed attr pvb_attributes; - { - str with - pstr_desc = - Pstr_primitive - { - pval_name; - pval_type = Ast_literal.type_float; - pval_loc = pvb_loc; - pval_attributes = []; - pval_prim = Some (Ast_external_mk.inline_float s); - }; - } - | ( Some attr, - Pexp_construct ({txt = Lident (("true" | "false") as txt)}, {txt = []}) - ) -> + let inline_const = + match has_inline_property with + | None -> None + | Some attr -> ( + match Ast_external_mk.inline_const_of_expression pvb_expr with + | None | Some (Const_bigint _) -> None + | Some c -> Some (attr, c)) + in + match inline_const with + | Some ((({loc = attr_loc}, _) as attr), c) -> succeed attr pvb_attributes; + let pval_type = + match c with + | Const_string _ -> Ast_literal.type_string () + | Const_int _ -> Ast_literal.type_int () + | Const_float _ -> Ast_literal.type_float + | Const_bool _ -> Ast_literal.type_bool () + | Const_bigint _ -> assert false + in { str with pstr_desc = Pstr_primitive - { - pval_name; - pval_type = Ast_literal.type_bool (); - pval_loc = pvb_loc; - pval_attributes = []; - pval_prim = Some (Ast_external_mk.inline_bool (txt = "true")); - }; + (Ast_external_mk.inline_const_declaration ~attr_loc + { + pval_name; + pval_type; + pval_loc = pvb_loc; + pval_attributes = []; + pval_prim = None; + } + pvb_expr); } | _ -> { diff --git a/compiler/frontend/ppx_entry.ml b/compiler/frontend/ppx_entry.ml index 701203f1b18..5e720a454b5 100644 --- a/compiler/frontend/ppx_entry.ml +++ b/compiler/frontend/ppx_entry.ml @@ -24,28 +24,6 @@ let unsafe_mapper = Bs_builtin_ppx.mapper -(* [json] payloads are syntax-level expressions until built-in FFI processing - consumes valid [@as(json`...`)] occurrences. Reject anything left only - after that processing, so generic attributes and ordinary expressions - cannot reinterpret them as strings. *) -let unconsumed_json_iterator = - let default = Ast_iterator.default_iterator in - { - default with - expr = - (fun self expression -> - match expression.pexp_desc with - | Pexp_constant (Pconst_json _) -> - Ast_payload.reject_json_literal ~loc:expression.pexp_loc - | _ -> default.expr self expression); - pat = - (fun self pattern -> - match pattern.ppat_desc with - | Ppat_constant (Pconst_json _) -> - Ast_payload.reject_json_literal ~loc:pattern.ppat_loc - | _ -> default.pat self pattern); - } - let rewrite_signature (ast : Parsetree.signature) : Parsetree.signature = Bs_ast_invariant.iter_warnings_on_sigi ast; Ast_config.process_sig ast; @@ -61,9 +39,11 @@ let rewrite_signature (ast : Parsetree.signature) : Parsetree.signature = if !Js_config.no_builtin_ppx then ast else let result = unsafe_mapper.signature unsafe_mapper ast in - unconsumed_json_iterator.signature unconsumed_json_iterator result; + Bs_ast_invariant.reject_unconsumed_json_on_signature + ~resolved_ffi_external:Ast_external.resolved_ffi_external result; (* Keep this check, since the check is not inexpensive*) - Bs_ast_invariant.emit_external_warnings_on_signature result; + Bs_ast_invariant.emit_external_warnings_on_signature + ~resolved_ffi_external:Ast_external.resolved_ffi_external result; result let rewrite_implementation (ast : Parsetree.structure) : Parsetree.structure = @@ -81,7 +61,9 @@ let rewrite_implementation (ast : Parsetree.structure) : Parsetree.structure = if !Js_config.no_builtin_ppx then ast else let result = unsafe_mapper.structure unsafe_mapper ast in - unconsumed_json_iterator.structure unconsumed_json_iterator result; + Bs_ast_invariant.reject_unconsumed_json_on_structure + ~resolved_ffi_external:Ast_external.resolved_ffi_external result; (* Keep this check since it is not inexpensive*) - Bs_ast_invariant.emit_external_warnings_on_structure result; + Bs_ast_invariant.emit_external_warnings_on_structure + ~resolved_ffi_external:Ast_external.resolved_ffi_external result; result diff --git a/compiler/gentype/translation.ml b/compiler/gentype/translation.ml index 10fd6225818..e16fe7b30c7 100644 --- a/compiler/gentype/translation.ml +++ b/compiler/gentype/translation.ml @@ -128,9 +128,8 @@ let translate_primitive ~config ~output_file_relative ~resolver ~type_env if !Debug.translation then Log_.item "Translate Primitive\n"; let value_name = (* external foo : someType = "abc" -- the extern name is "abc" *) - match value_description.val_prim with - | (Some (Prim_name name_of_extern) | Some (Prim_ffi {name = name_of_extern})) - when name_of_extern <> "" -> + match value_description.val_val.val_kind with + | Val_prim {prim_name = name_of_extern} when name_of_extern <> "" -> name_of_extern | _ -> value_description.val_id |> Ident.name in diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index bf1dad721a7..1ec95783c0a 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -288,7 +288,7 @@ module Val : sig val mk : ?loc:loc -> ?attrs:attrs -> - ?prim:primitive_repr -> + ?prim:str -> str -> core_type -> value_description diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index d9eabd9de6e..b37c6e0abaf 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -503,10 +503,9 @@ let default_iterator = type_extension = T.iter_type_extension; extension_constructor = T.iter_extension_constructor; value_description = - (fun this - {pval_name; pval_type; pval_prim = _; pval_loc; pval_attributes} - -> + (fun this {pval_name; pval_type; pval_prim; pval_loc; pval_attributes} -> iter_loc this pval_name; + Option.iter (iter_loc this) pval_prim; this.typ this pval_type; this.attributes this pval_attributes; this.location this pval_loc); diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index cc0da9a6325..11d5f575afc 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -511,7 +511,7 @@ let default_mapper = Val.mk (map_loc this pval_name) (this.typ this pval_type) ~attrs:(this.attributes this pval_attributes) ~loc:(this.location this pval_loc) - ?prim:pval_prim); + ?prim:(Option.map (map_loc this) pval_prim)); pat = P.map; expr = E.map; module_declaration = diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index d44ea4d7d07..6221886b961 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -1480,7 +1480,9 @@ let default_mapper = let prim = match pval_prim with | [] -> None - | [s] -> Some (Parsetree.Prim_name s) + | [s] -> + (* v0 carries no location for the primitive string *) + Some (Location.mknoloc s) | _ :: _ :: _ -> Location.raise_errorf ~loc:pval_loc "An external declaration can carry only a single primitive string" diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index 148a0eb0c0f..ee71aabea09 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -963,14 +963,7 @@ let default_mapper = let prim = match pval_prim with | None -> [] - | Some (Prim_name s) -> [s] - | Some (Prim_ffi _ | Prim_inline_const _) -> - (* External PPXes run before the frontend digests externals, and - nothing else crosses this bridge, so a digested external can - never legitimately reach it. *) - Location.raise_errorf ~loc:pval_loc - "External declarations already processed for compilation cannot \ - be converted for an external PPX" + | Some {txt} -> [txt] in Val.mk (map_loc this pval_name) (this.typ this pval_type) ~attrs:(this.attributes this pval_attributes) diff --git a/compiler/ml/external_ffi_types.ml b/compiler/ml/external_ffi_types.ml index fbfd4276130..40b61a187fa 100644 --- a/compiler/ml/external_ffi_types.ml +++ b/compiler/ml/external_ffi_types.ml @@ -40,7 +40,7 @@ type arg_type = External_arg_spec.attr type arg_label = External_arg_spec.label (* The declaration, as the attribute language states it. The backend - compiles it directly; digestion validates it with [check_decl]. *) + compiles it directly; resolution validates it with [check_decl]. *) type module_source = | Module_named of external_module_name (* @module("...") payload forms *) | Module_itself diff --git a/compiler/ml/external_ffi_types.mli b/compiler/ml/external_ffi_types.mli index a4b9afa1902..81f208da61f 100644 --- a/compiler/ml/external_ffi_types.mli +++ b/compiler/ml/external_ffi_types.mli @@ -40,7 +40,7 @@ type arg_type = External_arg_spec.attr type arg_label = External_arg_spec.label (* The declaration, as the attribute language states it. The backend - compiles it directly; digestion validates it with [check_decl]. *) + compiles it directly; resolution validates it with [check_decl]. *) type module_source = | Module_named of external_module_name (* @module("...") payload forms *) | Module_itself diff --git a/compiler/ml/oprint.ml b/compiler/ml/oprint.ml index 984c46832b9..fe4a81b827e 100644 --- a/compiler/ml/oprint.ml +++ b/compiler/ml/oprint.ml @@ -485,14 +485,14 @@ and print_out_sig_item ppf = function ppf td | Osig_value vd -> let kwd = if vd.oval_prim = None then "val" else "external" in - let pr_prim ppf (repr : Parsetree.primitive_repr option) = + let pr_prim ppf (prim : out_primitive option) = (* this OCaml-syntax debug printer shows the external's name only; the ReScript outcome printer renders the full attribute syntax *) - match repr with + match prim with | None -> () - | Some (Prim_name s) | Some (Prim_ffi {name = s}) -> + | Some (Oprim_intrinsic s) | Some (Oprim_external {name = s}) -> fprintf ppf "@ = \"%s\"" s - | Some (Prim_inline_const _) -> fprintf ppf "@ = \"#rescript-inline\"" + | Some (Oprim_inline_const _) -> fprintf ppf "@ = \"#rescript-inline\"" in fprintf ppf "@[<2>%s %a :@ %a%a%a@]" kwd value_ident vd.oval_name !out_type vd.oval_type pr_prim vd.oval_prim diff --git a/compiler/ml/outcometree.ml b/compiler/ml/outcometree.ml index eb36a5b8f91..4c4bf3564e5 100644 --- a/compiler/ml/outcometree.ml +++ b/compiler/ml/outcometree.ml @@ -112,9 +112,15 @@ and out_type_extension = { and out_val_decl = { oval_name: string; oval_type: out_type; - oval_prim: Parsetree.primitive_repr option; + oval_prim: out_primitive option; oval_attributes: out_attribute list; } + +(* A resolved primitive, so the printer can render its FFI attributes *) +and out_primitive = + | Oprim_intrinsic of string + | Oprim_external of {name: string; spec: External_ffi_types.t} + | Oprim_inline_const of External_ffi_types.inline_const and out_rec_status = Orec_not | Orec_first | Orec_next and out_ext_status = Oext_first | Oext_next | Oext_exception diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 58327b4b021..96dd295ba0e 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -511,26 +511,19 @@ and fun_param = { and value_description = { pval_name: string loc; pval_type: core_type; - pval_prim: primitive_repr option; + pval_prim: string loc option; + (* The primitive string as written: an intrinsic ("%identity", + "#raw_expr") or the JS name of an FFI external. Its FFI attributes + stay in [pval_attributes] and on the argument types; the type + checker resolves them through [Primitive.resolve_external]. *) pval_attributes: attributes; (* ... [@@id1] [@@id2] *) pval_loc: Location.t; } (* val x: T (prim = None) - external x: T = "s" (prim = Some _) + external x: T = "s" (prim = Some "s") *) -and primitive_repr = - | Prim_name of string - (* as written in the source: an intrinsic ("%identity", "#raw_expr") - or the not-yet-digested JS name of an FFI external *) - | Prim_ffi of {name: string; spec: External_ffi_types.t} - (* produced by the frontend digestion of an FFI external's - attributes; never observed by external PPXes, which run before - digestion *) - | Prim_inline_const of External_ffi_types.inline_const -(* an [@inline()] value declaration: a compile-time constant, - not an FFI; produced by digestion like [Prim_ffi] *) (* Type declarations *) and type_declaration = { diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index c188bc5438c..5415175d05d 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -971,12 +971,7 @@ and value_description ctxt f x = (fun f x -> match x.pval_prim with | None -> () - | Some (Prim_name s) -> pp f "@ =@ %a" constant_string s - | Some (Prim_ffi {name}) -> - pp f "@ =@ %a@ %a" constant_string name constant_string - "#rescript-external" - | Some (Prim_inline_const _) -> - pp f "@ =@ %a" constant_string "#rescript-inline") + | Some {txt} -> pp f "@ =@ %a" constant_string txt) x and extension ctxt f (s, e) = pp f "@[<2>[%%%s@ %a]@]" s.txt (payload ctxt) e diff --git a/compiler/ml/primitive.ml b/compiler/ml/primitive.ml index 94aa86f324c..28a2dc9bbfe 100644 --- a/compiler/ml/primitive.ml +++ b/compiler/ml/primitive.ml @@ -16,7 +16,6 @@ (* Description of primitive functions *) open Misc -open Parsetree type prim_kind = | Kind_intrinsic @@ -50,15 +49,19 @@ let coercible (impl : description) (intf : description) = External_ffi_types.inclusion_compatible impl_ffi intf_ffi | _ -> false -let parse_declaration (valdecl : Parsetree.value_description) ~arity - ~from_constructor = - let name, kind = - match valdecl.pval_prim with - | Some (Prim_name name) -> (name, Kind_intrinsic) - | Some (Prim_ffi {name; spec}) -> (name, Kind_external spec) - | Some (Prim_inline_const c) -> ("", Kind_inline_const c) - | None -> fatal_error "Primitive.parse_declaration" - in +type resolved_external = { + resolved_type: Parsetree.core_type; + resolved_attributes: Parsetree.attributes; + resolved_name: string; + resolved_kind: prim_kind; +} + +let resolve_external : + (Parsetree.value_description -> string -> resolved_external) ref = + ref (fun (_ : Parsetree.value_description) (_ : string) -> + fatal_error "Primitive.resolve_external: no resolver registered") + +let make ~name ~kind ~arity ~from_constructor = { prim_name = name; prim_arity = arity; @@ -70,10 +73,10 @@ let parse_declaration (valdecl : Parsetree.value_description) ~arity open Outcometree let print p osig_val_decl = - let repr : Parsetree.primitive_repr = + let prim = match p.prim_kind with - | Kind_intrinsic -> Prim_name p.prim_name - | Kind_external spec -> Prim_ffi {name = p.prim_name; spec} - | Kind_inline_const c -> Prim_inline_const c + | Kind_intrinsic -> Oprim_intrinsic p.prim_name + | Kind_external spec -> Oprim_external {name = p.prim_name; spec} + | Kind_inline_const c -> Oprim_inline_const c in - {osig_val_decl with oval_prim = Some repr; oval_attributes = []} + {osig_val_decl with oval_prim = Some prim; oval_attributes = []} diff --git a/compiler/ml/primitive.mli b/compiler/ml/primitive.mli index a0596d57ade..7aea5631c9b 100644 --- a/compiler/ml/primitive.mli +++ b/compiler/ml/primitive.mli @@ -36,12 +36,30 @@ val with_arity : (* Invariant [List.length d.prim_native_repr_args = d.prim_arity] *) -val parse_declaration : - Parsetree.value_description -> +val make : + name:string -> + kind:prim_kind -> arity:int -> from_constructor:bool -> description +type resolved_external = { + resolved_type: Parsetree.core_type; + (** The declared type, rewritten where FFI attributes erase or reshape + arguments ([@as] constants, [@ignore], [@obj] results). *) + resolved_attributes: Parsetree.attributes; + (** The declaration's attributes that resolution did not consume. *) + resolved_name: string; + resolved_kind: prim_kind; +} +(** An [external] declaration after its FFI attributes are applied. *) + +val resolve_external : + (Parsetree.value_description -> string -> resolved_external) ref +(** [!resolve_external value_desc prim] resolves an [external] declaration + whose primitive string is [prim] during type checking. FFI resolution lives in the frontend, above this library, which + registers it before any source is type checked. *) + val print : description -> Outcometree.out_val_decl -> Outcometree.out_val_decl val coercible : description -> description -> bool diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 57c9c0d35f3..6f6877ac33e 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -477,11 +477,7 @@ and value_description i ppf x = core_type (i + 1) ppf x.pval_type; match x.pval_prim with | None -> () - | Some (Prim_name s) -> string (i + 1) ppf s - | Some (Prim_ffi {name}) -> - string (i + 1) ppf name; - line (i + 1) ppf "\n" - | Some (Prim_inline_const _) -> line (i + 1) ppf "\n" + | Some {txt} -> string (i + 1) ppf txt and type_parameter i ppf (x, _variance) = core_type i ppf x diff --git a/compiler/ml/printtyped.ml b/compiler/ml/printtyped.ml index 73eb6f36bf9..b3399b18a9a 100644 --- a/compiler/ml/printtyped.ml +++ b/compiler/ml/printtyped.ml @@ -419,11 +419,7 @@ and value_description i ppf x = core_type (i + 1) ppf x.val_desc; match x.val_prim with | None -> () - | Some (Prim_name s) -> string (i + 1) ppf s - | Some (Prim_ffi {name}) -> - string (i + 1) ppf name; - line (i + 1) ppf "\n" - | Some (Prim_inline_const _) -> line (i + 1) ppf "\n" + | Some {txt} -> string (i + 1) ppf txt and type_parameter i ppf (x, _variance) = core_type i ppf x diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 230147cf6ab..85a097c4989 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -1890,16 +1890,30 @@ let parse_arity env _core_type ty = (* Translate a value declaration *) let transl_value_decl env loc valdecl = - let cty = Typetexp.transl_type_scheme env valdecl.pval_type in + let pval_type, pval_attributes, prim = + match valdecl.pval_prim with + | None -> (valdecl.pval_type, valdecl.pval_attributes, None) + | Some {txt} -> + let { + Primitive.resolved_type; + resolved_attributes; + resolved_name; + resolved_kind; + } = + !Primitive.resolve_external valdecl txt + in + (resolved_type, resolved_attributes, Some (resolved_name, resolved_kind)) + in + let cty = Typetexp.transl_type_scheme env pval_type in let ty = cty.ctyp_type in let v = - match valdecl.pval_prim with + match prim with | None when Env.is_in_signature env -> { val_type = ty; val_kind = Val_reg; Types.val_loc = loc; - val_attributes = valdecl.pval_attributes; + val_attributes = pval_attributes; } | None -> (* unreachable: `pval_prim = None` outside a signature can only arise @@ -1908,20 +1922,20 @@ let transl_value_decl env loc valdecl = declaration. A bare `val x: int` in a .res is also rejected at parse time. *) assert false - | Some _ -> - let arity, from_constructor = parse_arity env valdecl.pval_type ty in - let prim = Primitive.parse_declaration valdecl ~arity ~from_constructor in + | Some (name, kind) -> + let arity, from_constructor = parse_arity env pval_type ty in + let prim = Primitive.make ~name ~kind ~arity ~from_constructor in if prim.prim_arity = 0 && prim.prim_kind = Kind_intrinsic && (prim.prim_name = "" || (prim.prim_name.[0] <> '%' && prim.prim_name.[0] <> '#')) - then raise (Error (valdecl.pval_type.ptyp_loc, Null_arity_external)); + then raise (Error (pval_type.ptyp_loc, Null_arity_external)); { val_type = ty; val_kind = Val_prim prim; Types.val_loc = loc; - val_attributes = valdecl.pval_attributes; + val_attributes = pval_attributes; } in let id, newenv = @@ -1936,7 +1950,7 @@ let transl_value_decl env loc valdecl = val_val = v; val_prim = valdecl.pval_prim; val_loc = valdecl.pval_loc; - val_attributes = valdecl.pval_attributes; + val_attributes = pval_attributes; } in (desc, newenv) diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index 52e342afa4e..3af42ec0846 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -389,7 +389,7 @@ and value_description = { val_name: string loc; val_desc: core_type; val_val: Types.value_description; - val_prim: Parsetree.primitive_repr option; + val_prim: string Asttypes.loc option; val_loc: Location.t; val_attributes: attribute list; } diff --git a/compiler/ml/typedtree.mli b/compiler/ml/typedtree.mli index a65edca0887..fe856c985c0 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -507,7 +507,7 @@ and value_description = { val_name: string loc; val_desc: core_type; val_val: Types.value_description; - val_prim: Parsetree.primitive_repr option; + val_prim: string Asttypes.loc option; val_loc: Location.t; val_attributes: attributes; } diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index abf056739fe..3478b6942fd 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -418,9 +418,7 @@ module Sexp_ast = struct Sexp.list (match vd.pval_prim with | None -> [] - | Some (Prim_name s) -> [string s] - | Some (Prim_ffi {name}) -> [string name; string ""] - | Some (Prim_inline_const _) -> [string ""]); + | Some {txt} -> [string txt]); attributes vd.pval_attributes; ] diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 58408e54e96..676fc971e07 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -6264,8 +6264,9 @@ and parse_external_def ~attrs ~start_pos p = let prim = match Parser.peek p with | String s -> + let loc = mk_loc (Parser.start_pos p) (Parser.end_pos p) in Parser.next p; - Some (Parsetree.Prim_name s) + Some (Location.mkloc s loc) | _ -> Parser.err ~start_pos:equal_start ~end_pos:equal_end p (Diagnostics.message diff --git a/compiler/syntax/src/res_outcome_printer.ml b/compiler/syntax/src/res_outcome_printer.ml index 4a4d3595060..6307d73d623 100644 --- a/compiler/syntax/src/res_outcome_printer.ml +++ b/compiler/syntax/src/res_outcome_printer.ml @@ -474,7 +474,7 @@ let print_type_parameter_doc (typ, (co, cn)) = (if typ = "_" then Doc.text "_" else Doc.text ("'" ^ typ)); ] -(* Print a digested FFI declaration as the surface attributes the user +(* Print a resolved FFI declaration as the surface attributes the user wrote; the stored form is the declaration itself. *) let print_string_literal_doc s = Doc.text ("\"" ^ String.escaped s ^ "\"") @@ -505,7 +505,7 @@ let print_external_module_doc (emn : External_ffi_types.external_module_name) = let with_fields = import_attributes |> List.map (fun (k, v) -> - (* digestion stores the source key [type_] as [type]; other + (* resolution stores the source key [type_] as [type]; other keys are stored as written, including exotic ones from escaped idents (\"some-identifier"), which must print escaped again to be writable source *) @@ -608,14 +608,14 @@ let rec print_out_sig_item_doc ?(print_name_as_is = false) let ffi_attrs, keyword, prim_name = match value_decl.oval_prim with | None -> (Doc.nil, "let ", None) - | Some (Prim_name s) -> (Doc.nil, "external ", Some s) - | Some (Prim_inline_const c) -> + | Some (Oprim_intrinsic s) -> (Doc.nil, "external ", Some s) + | Some (Oprim_inline_const c) -> (* surface syntax: [@inline("hello") let f: string] *) ( Doc.concat [Doc.text "@inline("; print_inline_const_doc c; Doc.text ") "], "let ", None ) - | Some (Prim_ffi {name; spec}) -> ( + | Some (Oprim_external {name; spec}) -> ( match spec with | Ffi_obj_create _ -> (Doc.text "@obj ", "external ", Some name) | Ffi_bs (_params, return_wrapper, decl) -> diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 2f316770c66..fdc87297cd9 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -1332,10 +1332,8 @@ and print_value_description ~state value_description cmt_tbl = Doc.line; (let s = match value_description.pval_prim with - | Some (Prim_name s) | Some (Prim_ffi {name = s}) - -> - s - | Some (Prim_inline_const _) | None -> "" + | Some {txt} -> txt + | None -> "" in Doc.concat [Doc.text "\""; Doc.text s; Doc.text "\""]); ]); diff --git a/tests/build_tests/super_errors/expected/json_literal_external_typed_arg.res.expected b/tests/build_tests/super_errors/expected/json_literal_external_typed_arg.res.expected new file mode 100644 index 00000000000..445dd9c061b --- /dev/null +++ b/tests/build_tests/super_errors/expected/json_literal_external_typed_arg.res.expected @@ -0,0 +1,9 @@ + + We've found a bug for you! + /.../fixtures/json_literal_external_typed_arg.res:2:31-39 + + 1 │ type t + 2 │ @send external f: (t, @as(json`{"a": 1}`) int) => unit = "f" + 3 │ + + A `json` literal can only be used in an external attribute such as `@as` \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/warning_101_external_arg_attribute.res.expected b/tests/build_tests/super_errors/expected/warning_101_external_arg_attribute.res.expected new file mode 100644 index 00000000000..35931b3b86b --- /dev/null +++ b/tests/build_tests/super_errors/expected/warning_101_external_arg_attribute.res.expected @@ -0,0 +1,26 @@ + + Warning number 101 (configured as error) + /.../fixtures/warning_101_external_arg_attribute.res:2:27-29 + + 1 │ type t + 2 │ @send external first: (t, @as("x") int) => unit = "first" + 3 │ @send external second: (t, @as("y") int) => unit = "second" + 4 │ + + Unused attribute: @as +This attribute has no effect here. +For example, some attributes are only meaningful in externals. + + + + Warning number 101 (configured as error) + /.../fixtures/warning_101_external_arg_attribute.res:3:28-30 + + 1 │ type t + 2 │ @send external first: (t, @as("x") int) => unit = "first" + 3 │ @send external second: (t, @as("y") int) => unit = "second" + 4 │ + + Unused attribute: @as +This attribute has no effect here. +For example, some attributes are only meaningful in externals. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/json_literal_external_typed_arg.res b/tests/build_tests/super_errors/fixtures/json_literal_external_typed_arg.res new file mode 100644 index 00000000000..2c9abf4eb40 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/json_literal_external_typed_arg.res @@ -0,0 +1,2 @@ +type t +@send external f: (t, @as(json`{"a": 1}`) int) => unit = "f" diff --git a/tests/build_tests/super_errors/fixtures/warning_101_external_arg_attribute.res b/tests/build_tests/super_errors/fixtures/warning_101_external_arg_attribute.res new file mode 100644 index 00000000000..9b1b9ff9a02 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/warning_101_external_arg_attribute.res @@ -0,0 +1,3 @@ +type t +@send external first: (t, @as("x") int) => unit = "first" +@send external second: (t, @as("y") int) => unit = "second" diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml index 30038cf3d1e..d7625a55b15 100644 --- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml +++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml @@ -221,6 +221,33 @@ let test_ternary_roundtrips_through_ast0 _ = OUnit.assert_equal ["before"; "after"] (attr_names round_tripped.pexp_attributes) +let test_external_primitive_roundtrips_through_ast0 _ = + let typ = + Ast_helper.Typ.constr ~loc (Location.mknoloc (Longident.Lident "t")) [] + in + let external_ = + Ast_helper.Val.mk ~loc + ~attrs:[attr "send" (Parsetree.PStr [])] + ~prim:(located_string ~loc:(source_loc 10 16) "join") + (Location.mknoloc "join") typ + in + let wire = + Ast_mapper_to0.default_mapper.value_description + Ast_mapper_to0.default_mapper external_ + in + OUnit.assert_equal ["join"] wire.pval_prim; + OUnit.assert_bool "the FFI attribute stays on the wire" + (has_attr "send" wire.pval_attributes); + let round_tripped = + Ast_mapper_from0.default_mapper.value_description + Ast_mapper_from0.default_mapper wire + in + (match round_tripped.pval_prim with + | Some {txt = "join"} -> () + | _ -> assert_failure "Expected the primitive string after the v0 roundtrip"); + OUnit.assert_bool "the FFI attribute survives the v0 roundtrip" + (has_attr "send" round_tripped.pval_attributes) + let test_v0_ternary_marker_preserves_other_attribute_order _ = let ident name = Ast_helper0.Exp.ident ~loc (Location.mknoloc (Longident.Lident name)) @@ -2059,6 +2086,8 @@ let suites = >:: test_record_rest_roundtrips_through_ast0; "ternary_roundtrips_through_ast0" >:: test_ternary_roundtrips_through_ast0; + "external_primitive_roundtrips_through_ast0" + >:: test_external_primitive_roundtrips_through_ast0; "v0_ternary_marker_preserves_other_attribute_order" >:: test_v0_ternary_marker_preserves_other_attribute_order; "v0_if_without_alternate_stays_if" diff --git a/tests/ounit_tests/ounit_string_literal_tests.ml b/tests/ounit_tests/ounit_string_literal_tests.ml index 20381fdddd0..beb0d101188 100644 --- a/tests/ounit_tests/ounit_string_literal_tests.ml +++ b/tests/ounit_tests/ounit_string_literal_tests.ml @@ -335,9 +335,13 @@ let assert_external_json_literal ~expected constant = | _ -> OUnit.assert_failure "expected a JavaScript JSON literal expression" let inline_string semantic = - match Ast_external_mk.inline_string semantic with - | Prim_inline_const constant -> constant - | _ -> OUnit.assert_failure "expected an inline constant" + match + Ast_external_mk.inline_const_of_expression + (Ast_helper.Exp.constant + (Pconst_string (String_literal.string_from_semantic semantic))) + with + | Some constant -> constant + | None -> OUnit.assert_failure "expected an inline constant" let assert_js_global ~expected (expression : J.expression) = match expression.expression_desc with diff --git a/tests/syntax_tests/data/ast-mapping/Externals.res b/tests/syntax_tests/data/ast-mapping/Externals.res new file mode 100644 index 00000000000..865d5d8f824 --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/Externals.res @@ -0,0 +1,11 @@ +// External declarations keep their primitive string and FFI attributes as +// written through the v0 compatibility AST; the type checker resolves them. +external identity: 'a => 'a = "%identity" +@val external parseInt: string => int = "parseInt" +@send external join: (array, string) => string = "join" +@module("./local") external local: int => int = "local" +@scope("Math") @val external max: (float, float) => float = "max" +@obj external makeProps: (~name: string, ~age: int=?, unit) => _ = "" +@send +external on: (t, @as("click") _, unit => unit) => unit = "addEventListener" +@variadic @module("path") external join2: array => string = "join" diff --git a/tests/syntax_tests/data/ast-mapping/expected/Externals.res.txt b/tests/syntax_tests/data/ast-mapping/expected/Externals.res.txt new file mode 100644 index 00000000000..865d5d8f824 --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/expected/Externals.res.txt @@ -0,0 +1,11 @@ +// External declarations keep their primitive string and FFI attributes as +// written through the v0 compatibility AST; the type checker resolves them. +external identity: 'a => 'a = "%identity" +@val external parseInt: string => int = "parseInt" +@send external join: (array, string) => string = "join" +@module("./local") external local: int => int = "local" +@scope("Math") @val external max: (float, float) => float = "max" +@obj external makeProps: (~name: string, ~age: int=?, unit) => _ = "" +@send +external on: (t, @as("click") _, unit => unit) => unit = "addEventListener" +@variadic @module("path") external join2: array => string = "join"