Skip to content

Commit 608130c

Browse files
cristianocclaude
andcommitted
Address review: payload names in printed externals, key display, coverage
- Print the attribute payload when it names a different JS entity than the external's primitive string (@Val("foo"), @send("bar"), ...): two declarations differing only in payload were correctly rejected but displayed identically in inclusion errors. Pinned by the new Cross_external_payload_name fixture. - Map the stored import-attribute key `type` back to its source form `type_` when printing @module({from, with}) payloads, so the displayed record is writable source. - Register Cross_external_spec_mismatch and Cross_external_payload_name in the ERROR_VARIANTS.md Value_descriptions fixture list. - Add ounit coverage for External_ffi_types.inclusion_compatible: the optional-field codegen flag is ignored in both directions, and names, optionality, and field counts must match. Signed-Off-By: Cristiano Calcagno <ccrisccris@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCtQiaDijUqA2fujQXvKUw
1 parent 030ecde commit 608130c

7 files changed

Lines changed: 92 additions & 11 deletions

File tree

compiler/syntax/src/res_outcome_printer.ml

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,9 @@ let print_external_module_doc (emn : External_ffi_types.external_module_name) =
513513
let with_fields =
514514
import_attributes
515515
|> List.map (fun (k, v) ->
516+
(* digestion stores the source key [type_] as [type]; map it
517+
back so the printed record is writable source *)
518+
let k = if k = "type" then "type_" else k in
516519
Doc.concat
517520
[Doc.text k; Doc.text ": "; print_string_literal_doc v])
518521
in
@@ -555,18 +558,31 @@ let print_return_wrapper_doc (w : External_ffi_types.return_wrapper) =
555558
| Return_null_undefined_to_opt -> Doc.text "@return(nullable) "
556559

557560
let print_decl_kind_doc (kind : External_ffi_types.decl_kind)
558-
(module_ : External_ffi_types.module_source option) =
561+
(module_ : External_ffi_types.module_source option) ~prim_name =
562+
(* the attribute payload names the JS entity when it differs from the
563+
external's primitive string; print it back, or two declarations
564+
differing only in payload would display identically *)
565+
let with_payload attr name =
566+
if name = prim_name then Doc.text ("@" ^ attr ^ " ")
567+
else
568+
Doc.concat
569+
[
570+
Doc.text ("@" ^ attr ^ "(");
571+
print_string_literal_doc name;
572+
Doc.text ") ";
573+
]
574+
in
559575
match kind with
560-
| Decl_val _ -> (
576+
| Decl_val {name} -> (
561577
(* [@val] conflicts with bare [@module]; elsewhere it is the default and
562578
always legal to write *)
563579
match module_ with
564580
| Some Module_itself -> Doc.nil
565-
| _ -> Doc.text "@val ")
566-
| Decl_send _ -> Doc.text "@send "
567-
| Decl_new _ -> Doc.text "@new "
568-
| Decl_get _ -> Doc.text "@get "
569-
| Decl_set _ -> Doc.text "@set "
581+
| _ -> with_payload "val" name)
582+
| Decl_send {name} -> with_payload "send" name
583+
| Decl_new {name} -> with_payload "new" name
584+
| Decl_get {name} -> with_payload "get" name
585+
| Decl_set {name} -> with_payload "set" name
570586
| Decl_get_index -> Doc.text "@get_index "
571587
| Decl_set_index -> Doc.text "@set_index "
572588

@@ -578,10 +594,10 @@ let print_module_source_doc (module_ : External_ffi_types.module_source option)
578594
| Some (Module_named emn) -> print_external_module_doc emn
579595

580596
let print_external_decl_attrs_doc (decl : External_ffi_types.external_decl)
581-
(return_wrapper : External_ffi_types.return_wrapper) =
597+
(return_wrapper : External_ffi_types.return_wrapper) ~prim_name =
582598
Doc.concat
583599
[
584-
print_decl_kind_doc decl.kind decl.module_;
600+
print_decl_kind_doc decl.kind decl.module_ ~prim_name;
585601
print_module_source_doc decl.module_;
586602
print_scopes_doc decl.scopes;
587603
print_variadic_doc decl.variadic;
@@ -608,7 +624,7 @@ let rec print_out_sig_item_doc ?(print_name_as_is = false)
608624
match spec with
609625
| Ffi_obj_create _ -> (Doc.text "@obj ", "external ", Some name)
610626
| Ffi_bs (_params, return_wrapper, decl) ->
611-
( print_external_decl_attrs_doc decl return_wrapper,
627+
( print_external_decl_attrs_doc decl return_wrapper ~prim_name:name,
612628
"external ",
613629
Some name ))
614630
in

tests/ERROR_VARIANTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ Wrapper symptoms attached to inclusion failures. Source: [includemod.ml:23](../c
357357
| Variant | Status | Fixture | Notes |
358358
|---|---|---|---|
359359
| `Missing_field` || `super_errors_multi/Iface_missing_value` | |
360-
| `Value_descriptions` || `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Iface_value_arity_mismatch`, `super_errors_multi/Smoke_interface_mismatch`, `module_sig_value_arity_mismatch*.res` | Arity mismatches print a dedicated hint (implementation vs interface argument counts), including through aliases and nested function types. |
360+
| `Value_descriptions` || `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Iface_value_arity_mismatch`, `super_errors_multi/Smoke_interface_mismatch`, `super_errors_multi/Cross_external_spec_mismatch`, `super_errors_multi/Cross_external_payload_name`, `module_sig_value_arity_mismatch*.res` | Arity mismatches print a dedicated hint (implementation vs interface argument counts), including through aliases and nested function types. |
361361
| `Type_declarations` || `super_errors_multi/Iface_type_decl_record`, `super_errors_multi/Iface_type_decl_variant`, `RecordInclusion.res`, `type_decl_function_arity_mismatch.res` | |
362362
| `Extension_constructors` || `super_errors_multi/Iface_extension_constructors` | |
363363
| `Module_types` || `super_errors_multi/Iface_module_types` | |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
===== Payload.res =====
2+
3+
We've found a bug for you!
4+
/.../fixtures/Cross_external_payload_name/Payload.res:1:1-39
5+
6+
1 │ @val("foo") external f: int => int = ""
7+
2 │
8+
9+
The implementation /.../fixtures/Cross_external_payload_name/Payload.res
10+
does not match the interface /.../fixtures/Cross_external_payload_name/payload.cmi:
11+
Values do not match:
12+
@val("foo") external f: int => int = ""
13+
is not included in
14+
@val("bar") external f: int => int = ""
15+
/.../fixtures/Cross_external_payload_name/Payload.resi:1:1-39:
16+
Expected declaration
17+
/.../fixtures/Cross_external_payload_name/Payload.res:1:1-39:
18+
Actual declaration
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@val("foo") external f: int => int = ""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@val("bar") external f: int => int = ""
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: ))
2+
3+
let obj_params (fields : (string * bool option) list) :
4+
External_arg_spec.obj_params =
5+
Ext_list.map fields (fun (name, opt) ->
6+
{
7+
External_arg_spec.obj_arg_type = Nothing;
8+
obj_arg_label =
9+
(match opt with
10+
| None -> External_arg_spec.obj_label name
11+
| Some for_sure -> External_arg_spec.optional for_sure name);
12+
})
13+
14+
let obj fields = External_ffi_types.ffi_obj_create (obj_params fields)
15+
16+
let compatible = External_ffi_types.inclusion_compatible
17+
18+
let suites =
19+
__FILE__
20+
>::: [
21+
(* [for_sure_no_nested_option] is per-module codegen conservatism
22+
derived from each side's own view of the field's type; it plays
23+
no role in declaration compatibility, in either direction. *)
24+
( __LOC__ >:: fun _ ->
25+
OUnit.assert_bool "impl false, intf true"
26+
(compatible (obj [("x", Some false)]) (obj [("x", Some true)])) );
27+
( __LOC__ >:: fun _ ->
28+
OUnit.assert_bool "impl true, intf false"
29+
(compatible (obj [("x", Some true)]) (obj [("x", Some false)])) );
30+
( __LOC__ >:: fun _ ->
31+
OUnit.assert_bool "field names must match"
32+
(not
33+
(compatible (obj [("x", Some true)]) (obj [("y", Some true)])))
34+
);
35+
( __LOC__ >:: fun _ ->
36+
OUnit.assert_bool "optional vs required must match"
37+
(not (compatible (obj [("x", Some true)]) (obj [("x", None)]))) );
38+
( __LOC__ >:: fun _ ->
39+
OUnit.assert_bool "field count must match"
40+
(not
41+
(compatible
42+
(obj [("x", None)])
43+
(obj [("x", None); ("y", None)]))) );
44+
]

tests/ounit_tests/ounit_tests_main.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ let suites =
2525
Ounit_jsx_loc_tests.suites;
2626
Ounit_analysis_config_tests.suites;
2727
Ounit_analysis_references_tests.suites;
28+
Ounit_ffi_inclusion_tests.suites;
2829
]
2930

3031
let _ = OUnit.run_test_tt_main suites

0 commit comments

Comments
 (0)