diff --git a/CHANGELOG.md b/CHANGELOG.md index 3126ba3d180..44c3b61f1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Reanalyze: add glob pattern support for suppress/unsuppress configurations (e.g., `"src/generated/**"`). https://github.com/rescript-lang/rescript/pull/8277 - Add optional `~locales` and `~options` parameters to `String.localeCompare`. https://github.com/rescript-lang/rescript/pull/8287 +- Add new `sourceLocPos` and `sourceLocValuePath` magic constants, and allow them to be autoinjected from the call site into function arguments via `-allow-autofill-source-loc`. https://github.com/rescript-lang/rescript/pull/8303 #### :bug: Bug fix diff --git a/compiler/bsc/rescript_compiler_main.ml b/compiler/bsc/rescript_compiler_main.ml index 8d4113cebdf..540cd299293 100644 --- a/compiler/bsc/rescript_compiler_main.ml +++ b/compiler/bsc/rescript_compiler_main.ml @@ -336,6 +336,9 @@ let command_line_flags : (string * Bsc_args.spec * string) array = ( "-bs-noassertfalse", set Clflags.no_assert_false, "*internal* no code for assert false" ); + ( "-allow-autofill-source-loc", + set Clflags.allow_autofill_source_loc, + "*internal* Allow source-loc autofill for optional source-loc args" ); ( "-noassert", set Clflags.noassert, "*internal* Do not compile assertion checks" ); diff --git a/compiler/ml/clflags.ml b/compiler/ml/clflags.ml index f0cd88115e7..385920e9a4b 100644 --- a/compiler/ml/clflags.ml +++ b/compiler/ml/clflags.ml @@ -46,6 +46,8 @@ and ignore_parse_errors = ref false (* -ignore-parse-errors *) let dont_write_files = ref false (* set to true under ocamldoc *) +let allow_autofill_source_loc = ref false + let reset_dump_state () = dump_source := false; dump_parsetree := false; diff --git a/compiler/ml/clflags.mli b/compiler/ml/clflags.mli index 0cb5f1ea3e5..df374dbe824 100644 --- a/compiler/ml/clflags.mli +++ b/compiler/ml/clflags.mli @@ -22,6 +22,7 @@ val dump_typedtree : bool ref val dump_rawlambda : bool ref val dump_lambda : bool ref val dont_write_files : bool ref +val allow_autofill_source_loc : bool ref val keep_locs : bool ref val only_parse : bool ref val ignore_parse_errors : bool ref diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index db810d4f912..bc9b410b9e5 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -13,7 +13,14 @@ (* *) (**************************************************************************) -type loc_kind = Loc_FILE | Loc_LINE | Loc_MODULE | Loc_LOC | Loc_POS +type loc_kind = + | Loc_FILE + | Loc_LINE + | Loc_MODULE + | Loc_SOURCE_LOC_VALUE_PATH + | Loc_SOURCE_LOC_POS + | Loc_LOC + | Loc_POS type tag_info = | Blk_constructor of { @@ -699,13 +706,23 @@ let raise_kind = function | Raise_regular -> "raise" | Raise_reraise -> "reraise" -let lam_of_loc kind loc = +let lam_of_loc ?(root_path : Path.t option) ?(current_value_path = []) kind loc + = let loc_start = loc.Location.loc_start in + let loc_end = loc.loc_end in let file, lnum, cnum = Location.get_pos_info loc_start in + let _, end_lnum, end_cnum = Location.get_pos_info loc_end in let file = Filename.basename file in let enum = loc.Location.loc_end.Lexing.pos_cnum - loc_start.Lexing.pos_cnum + cnum in + let module_path = + match root_path with + | Some path -> Path.name path + | None -> + let name = Env.get_unit_name () in + if name = "" then Filename.remove_extension file else name + in match kind with | Loc_POS -> Lconst @@ -718,6 +735,24 @@ let lam_of_loc kind loc = Const_base (Const_int enum); ] )) | Loc_FILE -> Lconst (Const_immstring file) + | Loc_SOURCE_LOC_POS -> + Lconst + (Const_immstring + ([ + file; + string_of_int lnum; + string_of_int cnum; + string_of_int end_lnum; + string_of_int end_cnum; + ] + |> String.concat ";")) + | Loc_SOURCE_LOC_VALUE_PATH -> + let value_path = + match current_value_path with + | [] -> module_path + | _ -> String.concat "." (module_path :: current_value_path) + in + Lconst (Const_immstring value_path) | Loc_MODULE -> let filename = Filename.basename file in let name = Env.get_unit_name () in diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index d8eaf57be6f..81ccb39a854 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -17,7 +17,14 @@ open Asttypes -type loc_kind = Loc_FILE | Loc_LINE | Loc_MODULE | Loc_LOC | Loc_POS +type loc_kind = + | Loc_FILE + | Loc_LINE + | Loc_MODULE + | Loc_SOURCE_LOC_VALUE_PATH + | Loc_SOURCE_LOC_POS + | Loc_LOC + | Loc_POS type tag_info = | Blk_constructor of { @@ -419,4 +426,9 @@ val is_guarded : lambda -> bool val patch_guarded : lambda -> lambda -> lambda val raise_kind : raise_kind -> string -val lam_of_loc : loc_kind -> Location.t -> lambda +val lam_of_loc : + ?root_path:Path.t -> + ?current_value_path:string list -> + loc_kind -> + Location.t -> + lambda diff --git a/compiler/ml/predef.ml b/compiler/ml/predef.ml index 5913791eff9..30782fc187a 100644 --- a/compiler/ml/predef.ml +++ b/compiler/ml/predef.ml @@ -51,6 +51,10 @@ and ident_result = ident_create "result" and ident_dict = ident_create "dict" +and ident_source_loc_pos = ident_create "sourceLocPos" + +and ident_source_loc_value_path = ident_create "sourceLocValuePath" + and ident_bigint = ident_create "bigint" and ident_string = ident_create "string" @@ -67,6 +71,9 @@ let type_is_builtin_path_but_option (p : Path.t) : test = match p with | Pident {stamp} when stamp = ident_option.stamp -> For_sure_no | Pident {stamp} when stamp = ident_unit.stamp -> For_sure_no + | Pident {stamp} when stamp = ident_source_loc_pos.stamp -> For_sure_yes + | Pident {stamp} when stamp = ident_source_loc_value_path.stamp -> + For_sure_yes | Pident {stamp} when stamp >= ident_int.stamp && stamp <= ident_promise.stamp -> For_sure_yes @@ -94,6 +101,10 @@ and path_result = Pident ident_result and path_dict = Pident ident_dict +and path_source_loc_pos = Pident ident_source_loc_pos + +and path_source_loc_value_path = Pident ident_source_loc_value_path + and path_bigint = Pident ident_bigint and path_string = Pident ident_string @@ -371,6 +382,8 @@ let common_initial_env add_type add_extension empty_env = |> add_type ident_array decl_array |> add_type ident_list decl_list |> add_type ident_dict decl_dict + |> add_type ident_source_loc_pos decl_abstr + |> add_type ident_source_loc_value_path decl_abstr |> add_type ident_unknown decl_unknown |> add_exception ident_undefined_recursive_module [newgenty (Ttuple [type_string; type_int; type_int])] diff --git a/compiler/ml/predef.mli b/compiler/ml/predef.mli index 8e3330fe43c..6aefe24561a 100644 --- a/compiler/ml/predef.mli +++ b/compiler/ml/predef.mli @@ -45,6 +45,8 @@ val path_list : Path.t val path_option : Path.t val path_result : Path.t val path_dict : Path.t +val path_source_loc_pos : Path.t +val path_source_loc_value_path : Path.t val path_bigint : Path.t val path_extension_constructor : Path.t diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 0282f6e113c..020f6eeee8a 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -55,6 +55,8 @@ let string_of_loc_kind = function | Loc_FILE -> "loc_FILE" | Loc_LINE -> "loc_LINE" | Loc_MODULE -> "loc_MODULE" + | Loc_SOURCE_LOC_VALUE_PATH -> "loc_SOURCE_LOC_VALUE_PATH" + | Loc_SOURCE_LOC_POS -> "loc_SOURCE_LOC_POS" | Loc_POS -> "loc_POS" | Loc_LOC -> "loc_LOC" diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 078cbf133a0..b50b700d9c5 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -34,6 +34,21 @@ let transl_module = (fun _cc _rootpath _modl -> assert false : module_coercion -> Path.t option -> module_expr -> lambda) +let current_root_path = ref None +let current_value_path = ref [] + +let with_current_value_path pat f = + let path_segment = + match pat.pat_desc with + | Tpat_var (id, _) -> Some (Ident.name id) + | Tpat_alias (_, id, _) -> Some (Ident.name id) + | _ -> None + in + match path_segment with + | None -> f () + | Some name -> + Ext_ref.protect current_value_path (!current_value_path @ [name]) f + (* Compile an exception/extension definition *) let transl_extension_constructor env path ext = @@ -244,6 +259,8 @@ let primitives_table = ("%loc_LINE", Ploc Loc_LINE); ("%loc_POS", Ploc Loc_POS); ("%loc_MODULE", Ploc Loc_MODULE); + ("%loc_SOURCE_LOC_VALUE_PATH", Ploc Loc_SOURCE_LOC_VALUE_PATH); + ("%loc_SOURCE_LOC_POS", Ploc Loc_SOURCE_LOC_POS); (* BEGIN Triples for ref data type *) ("%makeref", Pmakeblock Lambda.ref_tag_info); ("%refset", Psetfield (0, Lambda.ref_field_set_info)); @@ -449,7 +466,10 @@ let transl_primitive loc p env ty = in match prim with | Ploc kind -> ( - let lam = lam_of_loc kind loc in + let lam = + lam_of_loc ?root_path:!current_root_path + ~current_value_path:!current_value_path kind loc + in match p.prim_arity with | 0 -> lam | 1 -> @@ -743,9 +763,14 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | _ -> k in wrap (Lprim (Praise k, [targ], e.exp_loc)) - | Ploc kind, [] -> lam_of_loc kind e.exp_loc + | Ploc kind, [] -> + lam_of_loc ?root_path:!current_root_path + ~current_value_path:!current_value_path kind e.exp_loc | Ploc kind, [arg1] -> - let lam = lam_of_loc kind arg1.exp_loc in + let lam = + lam_of_loc ?root_path:!current_root_path + ~current_value_path:!current_value_path kind arg1.exp_loc + in Lprim (Pmakeblock Blk_tuple, lam :: argl, e.exp_loc) | Ploc _, _ -> assert false | _, _ -> ( @@ -1076,7 +1101,7 @@ and transl_let rec_flag pat_expr_list body = let rec transl = function | [] -> body | {vb_pat = pat; vb_expr = expr; vb_attributes = attr; vb_loc} :: rem -> - let lam = transl_exp expr in + let lam = with_current_value_path pat (fun () -> transl_exp expr) in let lam = Translattribute.add_inline_attribute lam vb_loc attr in Matching.for_let pat.pat_loc lam pat (transl rem) in @@ -1092,7 +1117,7 @@ and transl_let rec_flag pat_expr_list body = Only variables are allowed as left-hand side of `let rec' *) in - let lam = transl_exp expr in + let lam = with_current_value_path pat (fun () -> transl_exp expr) in let lam = Translattribute.add_inline_attribute lam vb_loc vb_attributes in (id, lam) in diff --git a/compiler/ml/translcore.mli b/compiler/ml/translcore.mli index 1847a4883c4..b84678bd81c 100644 --- a/compiler/ml/translcore.mli +++ b/compiler/ml/translcore.mli @@ -34,6 +34,8 @@ val transl_primitive : val transl_extension_constructor : Env.t -> Path.t option -> Typedtree.extension_constructor -> Lambda.lambda +val current_root_path : Path.t option ref + (* Forward declaration -- to be filled in by Translmod.transl_module *) val transl_module : (Typedtree.module_coercion -> diff --git a/compiler/ml/translmod.ml b/compiler/ml/translmod.ml index 87471ac26bf..08c721f67c1 100644 --- a/compiler/ml/translmod.ml +++ b/compiler/ml/translmod.ml @@ -254,35 +254,38 @@ let rec compile_functor mexp coercion root_path loc = (* Compile a module expression *) and transl_module cc rootpath mexp = - List.iter (Translattribute.check_attribute_on_module mexp) mexp.mod_attributes; - let loc = mexp.mod_loc in - match mexp.mod_type with - | Mty_alias (Mta_absent, _) -> - apply_coercion loc Alias cc Lambda.lambda_module_alias - | _ -> ( - match mexp.mod_desc with - | Tmod_ident (path, _) -> - apply_coercion loc Strict cc - (Lambda.transl_module_path ~loc mexp.mod_env path) - | Tmod_structure str -> fst (transl_struct loc [] cc rootpath str) - | Tmod_functor _ -> compile_functor mexp cc rootpath loc - | Tmod_apply (funct, arg, ccarg) -> - let inlined_attribute, funct = - Translattribute.get_and_remove_inlined_attribute_on_module funct - in - apply_coercion loc Strict cc - (Lapply - { - ap_loc = loc; - ap_func = transl_module Tcoerce_none None funct; - ap_args = [transl_module ccarg None arg]; - ap_inlined = inlined_attribute; - ap_transformed_jsx = false; - }) - | Tmod_constraint (arg, _, _, ccarg) -> - transl_module (compose_coercions cc ccarg) rootpath arg - | Tmod_unpack (arg, _) -> - apply_coercion loc Strict cc (Translcore.transl_exp arg)) + Ext_ref.protect Translcore.current_root_path rootpath (fun () -> + List.iter + (Translattribute.check_attribute_on_module mexp) + mexp.mod_attributes; + let loc = mexp.mod_loc in + match mexp.mod_type with + | Mty_alias (Mta_absent, _) -> + apply_coercion loc Alias cc Lambda.lambda_module_alias + | _ -> ( + match mexp.mod_desc with + | Tmod_ident (path, _) -> + apply_coercion loc Strict cc + (Lambda.transl_module_path ~loc mexp.mod_env path) + | Tmod_structure str -> fst (transl_struct loc [] cc rootpath str) + | Tmod_functor _ -> compile_functor mexp cc rootpath loc + | Tmod_apply (funct, arg, ccarg) -> + let inlined_attribute, funct = + Translattribute.get_and_remove_inlined_attribute_on_module funct + in + apply_coercion loc Strict cc + (Lapply + { + ap_loc = loc; + ap_func = transl_module Tcoerce_none None funct; + ap_args = [transl_module ccarg None arg]; + ap_inlined = inlined_attribute; + ap_transformed_jsx = false; + }) + | Tmod_constraint (arg, _, _, ccarg) -> + transl_module (compose_coercions cc ccarg) rootpath arg + | Tmod_unpack (arg, _) -> + apply_coercion loc Strict cc (Translcore.transl_exp arg))) and transl_struct loc fields cc rootpath str = transl_structure loc fields cc rootpath str.str_final_env str.str_items diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index ee304dff7d7..2d4316819f2 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2239,14 +2239,98 @@ let extract_function_name funct = | Texp_ident (path, _, _) -> Some (Longident.parse (Path.name path)) | _ -> None +type autofill_source_loc_kind = Source_loc_pos | Source_loc_value_path + +let source_loc_kind env ty = + match (expand_head env ty).desc with + | Tconstr (path, [], _) when Path.same path Predef.path_source_loc_pos -> + Some Source_loc_pos + | Tconstr (path, [], _) when Path.same path Predef.path_source_loc_value_path + -> + Some Source_loc_value_path + | _ -> None + +let optional_source_loc_kind env ty = + match (expand_head env ty).desc with + | Tconstr (option_path, [inner], _) + when Path.same option_path Predef.path_option -> + source_loc_kind env inner + | _ -> None + type lazy_args = (Asttypes.arg_label * (unit -> Typedtree.expression) option) list type targs = (Asttypes.arg_label * Typedtree.expression option) list + +(* This Obj.magic models `%autofill` as an empty string (which represents "missing"). + The real source-loc payload is synthesized later at each call site. *) +let source_loc_default_arg ~loc _kind = + Ast_helper.Exp.apply ~loc + (Ast_helper.Exp.ident ~loc + (Location.mknoloc (Longident.Ldot (Longident.Lident "Obj", "magic")))) + [(Nolabel, Ast_helper.Exp.constant ~loc (Pconst_string ("", None)))] + +let source_loc_kind_of_pattern_annotation env spat = + match spat.ppat_desc with + | Ppat_constraint (_, sty) -> + let cty = Typetexp.transl_simple_type env false sty in + source_loc_kind env cty.ctyp_type + | _ -> None + +let autofill_default_loc expr = + match expr.pexp_desc with + | Pexp_extension ({txt = "autofill"; loc}, PStr []) -> Some loc + | Pexp_extension ({txt = "autofill"; loc}, _) -> + raise + (Error_forward + (Location.errorf ~loc + "`%%autofill` does not take a payload. Use `%%autofill`.")) + | _ -> None + +let invalid_source_loc_optional_arg ~loc = + raise + (Error_forward + (Location.errorf ~loc + "Optional `sourceLocPos` and `sourceLocValuePath` args must use \ + `=%%autofill`. Remove the optional/default syntax to make this a \ + regular required arg.")) + +let invalid_autofill_arg ~loc = + raise + (Error_forward + (Location.errorf ~loc + "`%%autofill` can only be used on args explicitly annotated as \ + `sourceLocPos` or `sourceLocValuePath`. Example: `~pos: \ + sourceLocPos=%%autofill`.")) + +(* We don't preserve `%autofill` in the function type. + Instead we reserve optional source-loc args for autofill and enforce that + `%autofill` is the only supported way to author them. A deeper integration + would represent autofill explicitly in the AST/type layer. *) let rec type_exp ?deprecated_context ~context ?recarg env sexp = (* We now delegate everything to type_expect *) type_expect ?deprecated_context ~context ?recarg env sexp (newvar ()) +(* For the `%autofill` case (or rather, the `~whatever: sourceLocPos=%autofill` case), + inject the special loc primitive at the application site. *) +and autofill_source_loc_arg ~apply_loc env ty kind = + let loc = {apply_loc with Location.loc_ghost = true} in + let expr = + Ast_helper.Exp.ident ~loc + (Location.mknoloc + (Longident.Lident + (match kind with + | Source_loc_pos -> "__SOURCE_LOC_POS__" + | Source_loc_value_path -> "__SOURCE_LOC_VALUE_PATH__"))) + in + option_some (type_expect ~context:None env expr (extract_option_type env ty)) + +and missing_optional_arg ~apply_loc env ty = + match optional_source_loc_kind env ty with + | Some kind when !Clflags.allow_autofill_source_loc -> + fun () -> autofill_source_loc_arg ~apply_loc env ty kind + | _ -> fun () -> option_none (instance env ty) Location.none + (* Typing of an expression with an expected type. This provide better error messages, and allows controlled propagation of return type information. @@ -2389,6 +2473,17 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) async; } -> assert (is_optional l); + (* Handle `%autofill` injection if needed. *) + let default = + match + ( source_loc_kind_of_pattern_annotation env spat, + autofill_default_loc default ) + with + | Some kind, Some _ -> source_loc_default_arg ~loc:default.pexp_loc kind + | Some _, None -> invalid_source_loc_optional_arg ~loc:default.pexp_loc + | None, Some loc -> invalid_autofill_arg ~loc + | None, None -> default + in (* default allowed only with optional argument *) let open Ast_helper in let default_loc = default.pexp_loc in @@ -2430,6 +2525,11 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) [Exp.case pat body] | Pexp_fun {arg_label = l; default = None; lhs = spat; rhs = sbody; arity; async} -> + let () = + match (is_optional l, source_loc_kind_of_pattern_annotation env spat) with + | true, Some _ -> invalid_source_loc_optional_arg ~loc:spat.ppat_loc + | _ -> () + in type_function ?in_function ~arity ~async loc sexp.pexp_attributes env ty_expected l [Ast_helper.Exp.case spat sbody] @@ -2449,7 +2549,9 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) let args, ty_res, fully_applied = match translate_unified_ops env funct sargs with | Some (targs, result_type) -> (targs, result_type, true) - | None -> type_application ~context total_app env funct sargs + | None -> + type_application ~context ~apply_loc:sexp.pexp_loc total_app env funct + sargs in end_def (); unify_var env (newvar ()) funct.exp_type; @@ -3531,7 +3633,7 @@ and translate_unified_ops (env : Env.t) (funct : Typedtree.expression) | _ -> None) | _ -> None -and type_application ~context total_app env funct (sargs : sargs) : +and type_application ~context ~apply_loc total_app env funct (sargs : sargs) : targs * Types.type_expr * bool = let result_type omitted ty_fun = List.fold_left @@ -3631,9 +3733,7 @@ and type_application ~context total_app env funct (sargs : sargs) : match (expand_head env ty_fun).desc with | Tarrow ({lbl; typ = t1}, t2, _, _) when is_optional lbl -> ignored := (lbl, t1, ty_fun.level) :: !ignored; - let arg = - (lbl, Some (fun () -> option_none (instance env t1) Location.none)) - in + let arg = (lbl, Some (missing_optional_arg ~apply_loc env t1)) in type_unknown_args max_arity ~args:(arg :: args) ~top_arity:None omitted t2 [] | _ -> collect_args () @@ -3708,9 +3808,8 @@ and type_application ~context total_app env funct (sargs : sargs) : | None -> if optional && (total_app || label_assoc Nolabel sargs) then ( ignored := (l, ty, lv) :: !ignored; - ( sargs, - omitted, - Some (fun () -> option_none (instance env ty) Location.none) )) + let arg = Some (missing_optional_arg ~apply_loc env ty) in + (sargs, omitted, arg)) else (sargs, (l, ty, lv) :: omitted, None) | Some (l', sarg0, sargs) -> if (not optional) && is_optional l' then diff --git a/packages/@rescript/runtime/Pervasives.res b/packages/@rescript/runtime/Pervasives.res index dcd2a29780a..a96856db370 100644 --- a/packages/@rescript/runtime/Pervasives.res +++ b/packages/@rescript/runtime/Pervasives.res @@ -50,6 +50,8 @@ external __LOC__: string = "%loc_LOC" external __FILE__: string = "%loc_FILE" external __LINE__: int = "%loc_LINE" external __MODULE__: string = "%loc_MODULE" +external __SOURCE_LOC_VALUE_PATH__: sourceLocValuePath = "%loc_SOURCE_LOC_VALUE_PATH" +external __SOURCE_LOC_POS__: sourceLocPos = "%loc_SOURCE_LOC_POS" external __POS__: (string, int, int, int) = "%loc_POS" external __LOC_OF__: 'a => (string, 'a) = "%loc_LOC" diff --git a/packages/@rescript/runtime/Stdlib.res b/packages/@rescript/runtime/Stdlib.res index 52f2771c699..35b7bf168c2 100644 --- a/packages/@rescript/runtime/Stdlib.res +++ b/packages/@rescript/runtime/Stdlib.res @@ -27,6 +27,7 @@ module Pair = Stdlib_Pair module Promise = Stdlib_Promise module RegExp = Stdlib_RegExp module Result = Stdlib_Result +module SourceLoc = Stdlib_SourceLoc module String = Stdlib_String module Symbol = Stdlib_Symbol module Type = Stdlib_Type diff --git a/packages/@rescript/runtime/Stdlib_SourceLoc.res b/packages/@rescript/runtime/Stdlib_SourceLoc.res new file mode 100644 index 00000000000..eb20048925a --- /dev/null +++ b/packages/@rescript/runtime/Stdlib_SourceLoc.res @@ -0,0 +1,47 @@ +module Pos = { + type t = sourceLocPos + + type decoded = { + file: string, + startLine: int, + startCol: int, + endLine: int, + endCol: int, + } + + external toString: t => string = "%identity" + + let decode = (value: t): option => { + switch value->toString->Stdlib_String.split(";") { + | [file, startLine, startCol, endLine, endCol] => + switch ( + startLine->Stdlib_Int.fromString, + startCol->Stdlib_Int.fromString, + endLine->Stdlib_Int.fromString, + endCol->Stdlib_Int.fromString, + ) { + | (Some(startLine), Some(startCol), Some(endLine), Some(endCol)) => + Some({file, startLine, startCol, endLine, endCol}) + | _ => None + } + | _ => None + } + } +} + +module ValuePath = { + type t = sourceLocValuePath + + external toString: t => string = "%identity" + + let segments = (value: t) => { + let value = value->toString + value === "" ? [] : value->Stdlib_String.split(".") + } + + let name = (value: t) => { + let segments = value->segments + let length = segments->Stdlib_Array.length + length === 0 ? "" : segments->Stdlib_Array.getUnsafe(length - 1) + } +} diff --git a/packages/@rescript/runtime/lib/es6/Stdlib.mjs b/packages/@rescript/runtime/lib/es6/Stdlib.mjs index 28ccab5bbc2..de9742d0eee 100644 --- a/packages/@rescript/runtime/lib/es6/Stdlib.mjs +++ b/packages/@rescript/runtime/lib/es6/Stdlib.mjs @@ -12,7 +12,7 @@ function assertEqual(a, b) { RE_EXN_ID: "Assert_failure", _1: [ "Stdlib.res", - 153, + 154, 4 ], Error: new Error() @@ -77,6 +77,8 @@ let $$RegExp; let Result; +let SourceLoc; + let $$String; let $$Symbol; @@ -153,6 +155,7 @@ export { $$Promise, $$RegExp, Result, + SourceLoc, $$String, $$Symbol, Type, diff --git a/packages/@rescript/runtime/lib/es6/Stdlib_SourceLoc.mjs b/packages/@rescript/runtime/lib/es6/Stdlib_SourceLoc.mjs new file mode 100644 index 00000000000..7aeebb546b8 --- /dev/null +++ b/packages/@rescript/runtime/lib/es6/Stdlib_SourceLoc.mjs @@ -0,0 +1,61 @@ + + +import * as Stdlib_Int from "./Stdlib_Int.mjs"; + +function decode(value) { + let match = value.split(";"); + if (match.length !== 5) { + return; + } + let file = match[0]; + let startLine = match[1]; + let startCol = match[2]; + let endLine = match[3]; + let endCol = match[4]; + let match$1 = Stdlib_Int.fromString(startLine); + let match$2 = Stdlib_Int.fromString(startCol); + let match$3 = Stdlib_Int.fromString(endLine); + let match$4 = Stdlib_Int.fromString(endCol); + if (match$1 !== undefined && match$2 !== undefined && match$3 !== undefined && match$4 !== undefined) { + return { + file: file, + startLine: match$1, + startCol: match$2, + endLine: match$3, + endCol: match$4 + }; + } +} + +let Pos = { + decode: decode +}; + +function segments(value) { + if (value === "") { + return []; + } else { + return value.split("."); + } +} + +function name(value) { + let segments$1 = segments(value); + let length = segments$1.length; + if (length === 0) { + return ""; + } else { + return segments$1[length - 1 | 0]; + } +} + +let ValuePath = { + segments: segments, + name: name +}; + +export { + Pos, + ValuePath, +} +/* No side effect */ diff --git a/packages/@rescript/runtime/lib/js/Stdlib.cjs b/packages/@rescript/runtime/lib/js/Stdlib.cjs index a36bc7bc7fc..b74e3c09421 100644 --- a/packages/@rescript/runtime/lib/js/Stdlib.cjs +++ b/packages/@rescript/runtime/lib/js/Stdlib.cjs @@ -12,7 +12,7 @@ function assertEqual(a, b) { RE_EXN_ID: "Assert_failure", _1: [ "Stdlib.res", - 153, + 154, 4 ], Error: new Error() @@ -77,6 +77,8 @@ let $$RegExp; let Result; +let SourceLoc; + let $$String; let $$Symbol; @@ -152,6 +154,7 @@ exports.Pair = Pair; exports.$$Promise = $$Promise; exports.$$RegExp = $$RegExp; exports.Result = Result; +exports.SourceLoc = SourceLoc; exports.$$String = $$String; exports.$$Symbol = $$Symbol; exports.Type = Type; diff --git a/packages/@rescript/runtime/lib/js/Stdlib_SourceLoc.cjs b/packages/@rescript/runtime/lib/js/Stdlib_SourceLoc.cjs new file mode 100644 index 00000000000..8a45fbc2152 --- /dev/null +++ b/packages/@rescript/runtime/lib/js/Stdlib_SourceLoc.cjs @@ -0,0 +1,59 @@ +'use strict'; + +let Stdlib_Int = require("./Stdlib_Int.cjs"); + +function decode(value) { + let match = value.split(";"); + if (match.length !== 5) { + return; + } + let file = match[0]; + let startLine = match[1]; + let startCol = match[2]; + let endLine = match[3]; + let endCol = match[4]; + let match$1 = Stdlib_Int.fromString(startLine); + let match$2 = Stdlib_Int.fromString(startCol); + let match$3 = Stdlib_Int.fromString(endLine); + let match$4 = Stdlib_Int.fromString(endCol); + if (match$1 !== undefined && match$2 !== undefined && match$3 !== undefined && match$4 !== undefined) { + return { + file: file, + startLine: match$1, + startCol: match$2, + endLine: match$3, + endCol: match$4 + }; + } +} + +let Pos = { + decode: decode +}; + +function segments(value) { + if (value === "") { + return []; + } else { + return value.split("."); + } +} + +function name(value) { + let segments$1 = segments(value); + let length = segments$1.length; + if (length === 0) { + return ""; + } else { + return segments$1[length - 1 | 0]; + } +} + +let ValuePath = { + segments: segments, + name: name +}; + +exports.Pos = Pos; +exports.ValuePath = ValuePath; +/* No side effect */ diff --git a/packages/artifacts.json b/packages/artifacts.json index a024c5ebc4e..19aa6eec0ef 100644 --- a/packages/artifacts.json +++ b/packages/artifacts.json @@ -7,6 +7,7 @@ "LICENSE", "LICENSE.MIT", "README.md", + "cli/bsc.exe", "cli/bsc.js", "cli/common/args.js", "cli/common/bins.js", @@ -14,6 +15,7 @@ "cli/common/runtime.js", "cli/rescript-tools.js", "cli/rescript.js", + "cli/rescript_compiler_main.exe", "docs/docson/build-schema.json", "package.json" ], @@ -182,6 +184,7 @@ "lib/es6/Stdlib_RegExp.mjs", "lib/es6/Stdlib_Result.mjs", "lib/es6/Stdlib_Set.mjs", + "lib/es6/Stdlib_SourceLoc.mjs", "lib/es6/Stdlib_String.mjs", "lib/es6/Stdlib_Symbol.mjs", "lib/es6/Stdlib_Type.mjs", @@ -356,6 +359,7 @@ "lib/js/Stdlib_RegExp.cjs", "lib/js/Stdlib_Result.cjs", "lib/js/Stdlib_Set.cjs", + "lib/js/Stdlib_SourceLoc.cjs", "lib/js/Stdlib_String.cjs", "lib/js/Stdlib_Symbol.cjs", "lib/js/Stdlib_Type.cjs", @@ -1192,6 +1196,10 @@ "lib/ocaml/Stdlib_Set.cmti", "lib/ocaml/Stdlib_Set.res", "lib/ocaml/Stdlib_Set.resi", + "lib/ocaml/Stdlib_SourceLoc.cmi", + "lib/ocaml/Stdlib_SourceLoc.cmj", + "lib/ocaml/Stdlib_SourceLoc.cmt", + "lib/ocaml/Stdlib_SourceLoc.res", "lib/ocaml/Stdlib_String.cmi", "lib/ocaml/Stdlib_String.cmj", "lib/ocaml/Stdlib_String.cmt", diff --git a/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt index 48c89535fbe..4afceb1ced9 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionFunctionArguments.res.txt @@ -232,8 +232,15 @@ Path someFnTakingVariant "tags": [], "detail": "someVariant", "documentation": {"kind": "markdown", "value": "```rescript\ntype someVariant = One | Two | Three(int, string)\n```"}, + "sortText": "A Some(_)", "insertText": "Some($0)", "insertTextFormat": 2 + }, { + "label": "SourceLoc", + "kind": 9, + "tags": [], + "detail": "module SourceLoc", + "documentation": null }] Complete src/CompletionFunctionArguments.res 60:44 diff --git a/tests/build_tests/super_errors/expected/source_loc_arg_autofill_missing_annotation.res.expected b/tests/build_tests/super_errors/expected/source_loc_arg_autofill_missing_annotation.res.expected new file mode 100644 index 00000000000..b096ab95a39 --- /dev/null +++ b/tests/build_tests/super_errors/expected/source_loc_arg_autofill_missing_annotation.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/source_loc_arg_autofill_missing_annotation.res:1:17-25 + + 1 │ let log = (~pos=%autofill) => pos + 2 │ + + `%autofill` can only be used on args explicitly annotated as `sourceLocPos` or `sourceLocValuePath`. Example: `~pos: sourceLocPos=%autofill`. diff --git a/tests/build_tests/super_errors/expected/source_loc_arg_autofill_wrong_type.res.expected b/tests/build_tests/super_errors/expected/source_loc_arg_autofill_wrong_type.res.expected new file mode 100644 index 00000000000..2ee41a67361 --- /dev/null +++ b/tests/build_tests/super_errors/expected/source_loc_arg_autofill_wrong_type.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/source_loc_arg_autofill_wrong_type.res:1:25-33 + + 1 │ let log = (~pos: string=%autofill) => pos + 2 │ + + `%autofill` can only be used on args explicitly annotated as `sourceLocPos` or `sourceLocValuePath`. Example: `~pos: sourceLocPos=%autofill`. diff --git a/tests/build_tests/super_errors/expected/source_loc_arg_missing_autofill.res.expected b/tests/build_tests/super_errors/expected/source_loc_arg_missing_autofill.res.expected new file mode 100644 index 00000000000..1840cb2b6d9 --- /dev/null +++ b/tests/build_tests/super_errors/expected/source_loc_arg_missing_autofill.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/source_loc_arg_missing_autofill.res:1:12-29 + + 1 │ let log = (~pos: sourceLocPos=?) => pos + 2 │ + + Optional `sourceLocPos` and `sourceLocValuePath` args must use `=%autofill`. Remove the optional/default syntax to make this a regular required arg. diff --git a/tests/build_tests/super_errors/expected/source_loc_arg_non_autofill_default.res.expected b/tests/build_tests/super_errors/expected/source_loc_arg_non_autofill_default.res.expected new file mode 100644 index 00000000000..2d4b0e9e11f --- /dev/null +++ b/tests/build_tests/super_errors/expected/source_loc_arg_non_autofill_default.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/source_loc_arg_non_autofill_default.res:1:31-48 + + 1 │ let log = (~pos: sourceLocPos=__SOURCE_LOC_POS__) => pos + 2 │ + + Optional `sourceLocPos` and `sourceLocValuePath` args must use `=%autofill`. Remove the optional/default syntax to make this a regular required arg. diff --git a/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_missing_annotation.res b/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_missing_annotation.res new file mode 100644 index 00000000000..28a76190500 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_missing_annotation.res @@ -0,0 +1 @@ +let log = (~pos=%autofill) => pos diff --git a/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_wrong_type.res b/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_wrong_type.res new file mode 100644 index 00000000000..6385a4ce9a6 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/source_loc_arg_autofill_wrong_type.res @@ -0,0 +1 @@ +let log = (~pos: string=%autofill) => pos diff --git a/tests/build_tests/super_errors/fixtures/source_loc_arg_missing_autofill.res b/tests/build_tests/super_errors/fixtures/source_loc_arg_missing_autofill.res new file mode 100644 index 00000000000..750ae425813 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/source_loc_arg_missing_autofill.res @@ -0,0 +1 @@ +let log = (~pos: sourceLocPos=?) => pos diff --git a/tests/build_tests/super_errors/fixtures/source_loc_arg_non_autofill_default.res b/tests/build_tests/super_errors/fixtures/source_loc_arg_non_autofill_default.res new file mode 100644 index 00000000000..7078893e72e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/source_loc_arg_non_autofill_default.res @@ -0,0 +1 @@ +let log = (~pos: sourceLocPos=__SOURCE_LOC_POS__) => pos diff --git a/tests/tests/src/source_loc_no_autofill_test.mjs b/tests/tests/src/source_loc_no_autofill_test.mjs new file mode 100644 index 00000000000..5e7a03dde26 --- /dev/null +++ b/tests/tests/src/source_loc_no_autofill_test.mjs @@ -0,0 +1,29 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; +import * as Stdlib_SourceLoc from "@rescript/runtime/lib/es6/Stdlib_SourceLoc.mjs"; + +function capture(posOpt, valuePathOpt) { + let pos = posOpt !== undefined ? posOpt : ""; + let valuePath = valuePathOpt !== undefined ? valuePathOpt : ""; + return [ + pos, + valuePath + ]; +} + +Mocha.describe("SourceLoc without -allow-autofill-source-loc", () => { + Mocha.test("missing args fall back to empty source loc values", () => { + let match = capture(undefined, undefined); + let valuePath = match[1]; + Test_utils.eq("File \"source_loc_no_autofill_test.res\", line 12, characters 7-14", Stdlib_SourceLoc.Pos.decode(match[0]), undefined); + Test_utils.eq("File \"source_loc_no_autofill_test.res\", line 13, characters 7-14", Stdlib_SourceLoc.ValuePath.segments(valuePath), []); + Test_utils.eq("File \"source_loc_no_autofill_test.res\", line 14, characters 7-14", Stdlib_SourceLoc.ValuePath.name(valuePath), ""); + }); +}); + +export { + capture, +} +/* Not a pure module */ diff --git a/tests/tests/src/source_loc_no_autofill_test.res b/tests/tests/src/source_loc_no_autofill_test.res new file mode 100644 index 00000000000..dc93c245df0 --- /dev/null +++ b/tests/tests/src/source_loc_no_autofill_test.res @@ -0,0 +1,16 @@ +open Mocha +open Test_utils + +let capture = (~pos: sourceLocPos=%autofill, ~valuePath: sourceLocValuePath=%autofill) => ( + pos, + valuePath, +) + +describe("SourceLoc without -allow-autofill-source-loc", () => { + test("missing args fall back to empty source loc values", () => { + let (pos, valuePath) = capture() + eq(__LOC__, SourceLoc.Pos.decode(pos), None) + eq(__LOC__, SourceLoc.ValuePath.segments(valuePath), []) + eq(__LOC__, SourceLoc.ValuePath.name(valuePath), "") + }) +}) diff --git a/tests/tests/src/source_loc_test.mjs b/tests/tests/src/source_loc_test.mjs new file mode 100644 index 00000000000..629726ea51a --- /dev/null +++ b/tests/tests/src/source_loc_test.mjs @@ -0,0 +1,125 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; +import * as Stdlib_SourceLoc from "@rescript/runtime/lib/es6/Stdlib_SourceLoc.mjs"; + +function capture(posOpt, valuePathOpt) { + let pos = posOpt !== undefined ? posOpt : ""; + let valuePath = valuePathOpt !== undefined ? valuePathOpt : ""; + return [ + pos, + valuePath + ]; +} + +let topLevelBinding = capture("source_loc_test.res;16;22;16;31", "Source_loc_test.topLevelBinding"); + +let topLevelExpressionCapture = { + contents: undefined +}; + +function storeTopLevelExpression(posOpt, valuePathOpt) { + let pos = posOpt !== undefined ? posOpt : ""; + let valuePath = valuePathOpt !== undefined ? valuePathOpt : ""; + topLevelExpressionCapture.contents = [ + pos, + valuePath + ]; +} + +storeTopLevelExpression("source_loc_test.res;27;0;27;25", "Source_loc_test"); + +let nestedBinding = capture("source_loc_test.res;30;22;30;31", "Source_loc_test.Nested.nestedBinding"); + +let topLevelExpressionCapture$1 = { + contents: undefined +}; + +function storeTopLevelExpression$1(posOpt, valuePathOpt) { + let pos = posOpt !== undefined ? posOpt : ""; + let valuePath = valuePathOpt !== undefined ? valuePathOpt : ""; + topLevelExpressionCapture$1.contents = [ + pos, + valuePath + ]; +} + +storeTopLevelExpression$1("source_loc_test.res;41;2;41;27", "Source_loc_test.Nested"); + +let Nested = { + nestedBinding: nestedBinding, + topLevelExpressionCapture: topLevelExpressionCapture$1, + storeTopLevelExpression: storeTopLevelExpression$1 +}; + +function expectCapture(loc, param, expectedValuePath) { + let match = Stdlib_SourceLoc.Pos.decode(param[0]); + if (match !== undefined) { + Test_utils.eq(loc, match.file, "source_loc_test.res"); + } else { + Test_utils.ok(loc, false); + } + Test_utils.eq(loc, param[1], expectedValuePath); +} + +Mocha.describe("SourceLoc", () => { + Mocha.test("Pos.decode parses sourceLocPos", () => { + let decoded = Stdlib_SourceLoc.Pos.decode("demo.res;1;2;3;4"); + Test_utils.eq("File \"source_loc_test.res\", line 57, characters 6-13", decoded, { + file: "demo.res", + startLine: 1, + startCol: 2, + endLine: 3, + endCol: 4 + }); + }); + Mocha.test("Pos.decode rejects malformed sourceLocPos", () => Test_utils.eq("File \"source_loc_test.res\", line 70, characters 7-14", Stdlib_SourceLoc.Pos.decode("bad"), undefined)); + Mocha.test("ValuePath helpers expose segments and name", () => { + let valuePath = "Demo.Nested.run"; + Test_utils.eq("File \"source_loc_test.res\", line 75, characters 7-14", Stdlib_SourceLoc.ValuePath.segments(valuePath), [ + "Demo", + "Nested", + "run" + ]); + Test_utils.eq("File \"source_loc_test.res\", line 76, characters 7-14", Stdlib_SourceLoc.ValuePath.name(valuePath), "run"); + }); + Mocha.test("ValuePath helpers treat empty strings as missing", () => { + let valuePath = ""; + Test_utils.eq("File \"source_loc_test.res\", line 81, characters 7-14", Stdlib_SourceLoc.ValuePath.segments(valuePath), []); + Test_utils.eq("File \"source_loc_test.res\", line 82, characters 7-14", Stdlib_SourceLoc.ValuePath.name(valuePath), ""); + }); + Mocha.test("source loc autofill works for a top-level let binding", () => expectCapture("File \"source_loc_test.res\", line 86, characters 18-25", topLevelBinding, "Source_loc_test.topLevelBinding")); + Mocha.test("source loc autofill works for a top-level expression", () => { + let capture = topLevelExpressionCapture.contents; + if (capture !== undefined) { + return expectCapture("File \"source_loc_test.res\", line 91, characters 37-44", capture, "Source_loc_test"); + } else { + return Test_utils.ok("File \"source_loc_test.res\", line 92, characters 17-24", false); + } + }); + Mocha.test("source loc autofill works for a nested module let binding", () => expectCapture("File \"source_loc_test.res\", line 97, characters 18-25", nestedBinding, "Source_loc_test.Nested.nestedBinding")); + Mocha.test("source loc autofill works for a nested module expression", () => { + let capture = topLevelExpressionCapture$1.contents; + if (capture !== undefined) { + return expectCapture("File \"source_loc_test.res\", line 102, characters 37-44", capture, "Source_loc_test.Nested"); + } else { + return Test_utils.ok("File \"source_loc_test.res\", line 103, characters 17-24", false); + } + }); + Mocha.test("explicit source loc args override autofill", () => { + let match = capture("", ""); + Test_utils.eq("File \"source_loc_test.res\", line 112, characters 7-14", Stdlib_SourceLoc.Pos.decode(match[0]), undefined); + Test_utils.eq("File \"source_loc_test.res\", line 113, characters 7-14", Stdlib_SourceLoc.ValuePath.segments(match[1]), []); + }); +}); + +export { + capture, + topLevelBinding, + topLevelExpressionCapture, + storeTopLevelExpression, + Nested, + expectCapture, +} +/* topLevelBinding Not a pure module */ diff --git a/tests/tests/src/source_loc_test.res b/tests/tests/src/source_loc_test.res new file mode 100644 index 00000000000..9de6587a43a --- /dev/null +++ b/tests/tests/src/source_loc_test.res @@ -0,0 +1,115 @@ +@@config({flags: ["-allow-autofill-source-loc"]}) + +open Mocha +open Test_utils + +external unsafeSourceLocPos: string => sourceLocPos = "%identity" +external unsafeSourceLocValuePath: string => sourceLocValuePath = "%identity" + +type sourceLocCapture = (sourceLocPos, sourceLocValuePath) + +let capture = (~pos: sourceLocPos=%autofill, ~valuePath: sourceLocValuePath=%autofill) => ( + pos, + valuePath, +) + +let topLevelBinding = capture() + +let topLevelExpressionCapture = ref((None: option)) + +let storeTopLevelExpression = ( + ~pos: sourceLocPos=%autofill, + ~valuePath: sourceLocValuePath=%autofill, +) => { + topLevelExpressionCapture.contents = Some((pos, valuePath)) +} + +storeTopLevelExpression() + +module Nested = { + let nestedBinding = capture() + + let topLevelExpressionCapture = ref((None: option)) + + let storeTopLevelExpression = ( + ~pos: sourceLocPos=%autofill, + ~valuePath: sourceLocValuePath=%autofill, + ) => { + topLevelExpressionCapture.contents = Some((pos, valuePath)) + } + + storeTopLevelExpression() +} + +let expectCapture = (loc, (pos, valuePath): sourceLocCapture, expectedValuePath) => { + switch SourceLoc.Pos.decode(pos) { + | Some({file}) => eq(loc, file, "source_loc_test.res") + | None => ok(loc, false) + } + + eq(loc, SourceLoc.ValuePath.toString(valuePath), expectedValuePath) +} + +describe("SourceLoc", () => { + test("Pos.decode parses sourceLocPos", () => { + let decoded = SourceLoc.Pos.decode(unsafeSourceLocPos("demo.res;1;2;3;4")) + eq( + __LOC__, + decoded, + Some({ + file: "demo.res", + startLine: 1, + startCol: 2, + endLine: 3, + endCol: 4, + }), + ) + }) + + test("Pos.decode rejects malformed sourceLocPos", () => { + eq(__LOC__, SourceLoc.Pos.decode(unsafeSourceLocPos("bad")), None) + }) + + test("ValuePath helpers expose segments and name", () => { + let valuePath = unsafeSourceLocValuePath("Demo.Nested.run") + eq(__LOC__, SourceLoc.ValuePath.segments(valuePath), ["Demo", "Nested", "run"]) + eq(__LOC__, SourceLoc.ValuePath.name(valuePath), "run") + }) + + test("ValuePath helpers treat empty strings as missing", () => { + let valuePath = unsafeSourceLocValuePath("") + eq(__LOC__, SourceLoc.ValuePath.segments(valuePath), []) + eq(__LOC__, SourceLoc.ValuePath.name(valuePath), "") + }) + + test("source loc autofill works for a top-level let binding", () => { + expectCapture(__LOC__, topLevelBinding, "Source_loc_test.topLevelBinding") + }) + + test("source loc autofill works for a top-level expression", () => { + switch topLevelExpressionCapture.contents { + | Some(capture) => expectCapture(__LOC__, capture, "Source_loc_test") + | None => ok(__LOC__, false) + } + }) + + test("source loc autofill works for a nested module let binding", () => { + expectCapture(__LOC__, Nested.nestedBinding, "Source_loc_test.Nested.nestedBinding") + }) + + test("source loc autofill works for a nested module expression", () => { + switch Nested.topLevelExpressionCapture.contents { + | Some(capture) => expectCapture(__LOC__, capture, "Source_loc_test.Nested") + | None => ok(__LOC__, false) + } + }) + + test("explicit source loc args override autofill", () => { + let (pos, valuePath) = capture( + ~pos=unsafeSourceLocPos(""), + ~valuePath=unsafeSourceLocValuePath(""), + ) + eq(__LOC__, SourceLoc.Pos.decode(pos), None) + eq(__LOC__, SourceLoc.ValuePath.segments(valuePath), []) + }) +}) diff --git a/tests/tests/src/test_per.res b/tests/tests/src/test_per.res index b53ba0515de..ade84b0b60e 100644 --- a/tests/tests/src/test_per.res +++ b/tests/tests/src/test_per.res @@ -18,6 +18,8 @@ external __LOC__: string = "%loc_LOC" external __MODULE__: string = "%loc_FILE" external __LINE__: int = "%loc_LINE" external __MODULE__: string = "%loc_MODULE" +external __SOURCE_LOC_VALUE_PATH__: sourceLocValuePath = "%loc_SOURCE_LOC_VALUE_PATH" +external __SOURCE_LOC_POS__: sourceLocPos = "%loc_SOURCE_LOC_POS" external __POS__: (string, int, int, int) = "%loc_POS" external __LOC_OF__: 'a => (string, 'a) = "%loc_LOC"