diff --git a/ocaml/driver/compile_common.ml b/ocaml/driver/compile_common.ml index babf71d24ae..8b115ceb4f6 100644 --- a/ocaml/driver/compile_common.ml +++ b/ocaml/driver/compile_common.ml @@ -64,9 +64,7 @@ let typecheck_intf info ast = let tsg = ast |> Typemod.type_interface - ~sourcefile:info.source_file - info.module_name - info.env + ~sourcefile:info.source_file info.module_name info.env |> print_if info.ppf_dump Clflags.dump_typedtree Printtyped.interface in let sg = tsg.Typedtree.sig_type in @@ -87,8 +85,14 @@ let emit_signature info ast tsg = let kind : Cmi_format.kind = if !Clflags.as_parameter then Parameter - else - Normal { cmi_impl = info.module_name } + else begin + let cmi_arg_for = + match !Clflags.as_argument_for with + | Some arg_type -> Some (Compilation_unit.Name.of_string arg_type) + | None -> None + in + Normal { cmi_impl = info.module_name; cmi_arg_for } + end in let alerts = Builtin_attributes.alerts_of_sig ast in Env.save_signature ~alerts tsg.Typedtree.sig_type diff --git a/ocaml/driver/main_args.ml b/ocaml/driver/main_args.ml index 2a91eb631e8..434cc33b63a 100644 --- a/ocaml/driver/main_args.ml +++ b/ocaml/driver/main_args.ml @@ -659,6 +659,11 @@ let mk_as_parameter f = " Compiles the interface as a parameter for an open module." ;; +let mk_as_argument_for f = + "-as-argument-for", Arg.String f, + " Compiles the module as an argument for the named parameter." +;; + let mk_use_prims f = "-use-prims", Arg.String f, " (undocumented)" @@ -965,6 +970,7 @@ end module type Compiler_options = sig val _a : unit -> unit val _annot : unit -> unit + val _as_argument_for : string -> unit val _as_parameter : unit -> unit val _binannot : unit -> unit val _binannot_cms : unit -> unit @@ -1165,6 +1171,7 @@ struct mk_absname F._absname; mk_no_absname F._no_absname; mk_annot F._annot; + mk_as_argument_for F._as_argument_for; mk_as_parameter F._as_parameter; mk_binannot F._binannot; mk_binannot_cms F._binannot_cms; @@ -1383,6 +1390,7 @@ struct mk_afl_instrument F._afl_instrument; mk_afl_inst_ratio F._afl_inst_ratio; mk_annot F._annot; + mk_as_argument_for F._as_argument_for; mk_as_parameter F._as_parameter; mk_binannot F._binannot; mk_binannot_cms F._binannot_cms; @@ -1970,6 +1978,7 @@ module Default = struct let _annot = set annotations let _args = Arg.read_arg let _args0 = Arg.read_arg0 + let _as_argument_for s = as_argument_for := Some s let _as_parameter = set as_parameter let _binannot = set binary_annotations let _binannot_cms = set binary_annotations_cms diff --git a/ocaml/driver/main_args.mli b/ocaml/driver/main_args.mli index dfb2797bb7a..7e136ed0abc 100644 --- a/ocaml/driver/main_args.mli +++ b/ocaml/driver/main_args.mli @@ -88,6 +88,7 @@ end module type Compiler_options = sig val _a : unit -> unit val _annot : unit -> unit + val _as_argument_for : string -> unit val _as_parameter : unit -> unit val _binannot : unit -> unit val _binannot_cms : unit -> unit diff --git a/ocaml/file_formats/cmi_format.ml b/ocaml/file_formats/cmi_format.ml index 1e0b86aa173..c32599d0024 100644 --- a/ocaml/file_formats/cmi_format.ml +++ b/ocaml/file_formats/cmi_format.ml @@ -23,6 +23,7 @@ type pers_flags = type kind = | Normal of { cmi_impl : Compilation_unit.t; + cmi_arg_for : Compilation_unit.Name.t option; } | Parameter diff --git a/ocaml/file_formats/cmi_format.mli b/ocaml/file_formats/cmi_format.mli index 72fef143c25..d80e47863fe 100644 --- a/ocaml/file_formats/cmi_format.mli +++ b/ocaml/file_formats/cmi_format.mli @@ -23,6 +23,7 @@ type pers_flags = type kind = | Normal of { cmi_impl : Compilation_unit.t; + cmi_arg_for : Compilation_unit.Name.t option; } | Parameter diff --git a/ocaml/ocamldoc/odoc_analyse.ml b/ocaml/ocamldoc/odoc_analyse.ml index 83845098fb4..82d92ab0267 100644 --- a/ocaml/ocamldoc/odoc_analyse.ml +++ b/ocaml/ocamldoc/odoc_analyse.ml @@ -122,7 +122,9 @@ let process_interface_file sourcefile = Pparse.file ~tool_name inputfile (no_docstring Parse.interface) Pparse.Signature in - let sg = Typemod.type_interface ~sourcefile compilation_unit (initial_env()) ast in + let sg = + Typemod.type_interface ~sourcefile compilation_unit (initial_env()) ast + in Warnings.check_fatal (); (ast, sg, inputfile) diff --git a/ocaml/testsuite/tests/templates/basic/bad_arg_impl.ml b/ocaml/testsuite/tests/templates/basic/bad_arg_impl.ml new file mode 100644 index 00000000000..86371fc4034 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/bad_arg_impl.ml @@ -0,0 +1,4 @@ +type t = unit + +let empty = () +let append () () = `Banana diff --git a/ocaml/testsuite/tests/templates/basic/bad_arg_impl.reference b/ocaml/testsuite/tests/templates/basic/bad_arg_impl.reference new file mode 100644 index 00000000000..128ba2632bd --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/bad_arg_impl.reference @@ -0,0 +1,12 @@ +File "bad_arg_impl.ml", line 1: +Error: The argument module bad_arg_impl.ml + does not match the parameter signature monoid.cmi: + Values do not match: + val append : unit -> unit -> [> `Banana ] + is not included in + val append : t -> t -> t + The type unit -> unit -> [> `Banana ] is not compatible with the type + t -> t -> t + Type [> `Banana ] is not compatible with type t = unit + File "monoid.mli", line 4, characters 0-24: Expected declaration + File "bad_arg_impl.ml", line 4, characters 4-10: Actual declaration diff --git a/ocaml/testsuite/tests/templates/basic/bad_arg_intf.mli b/ocaml/testsuite/tests/templates/basic/bad_arg_intf.mli new file mode 100644 index 00000000000..13c9b7ff4ab --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/bad_arg_intf.mli @@ -0,0 +1,4 @@ +type t + +val empty : t +val append : t -> t -> [ `Banana ] diff --git a/ocaml/testsuite/tests/templates/basic/bad_arg_intf.reference b/ocaml/testsuite/tests/templates/basic/bad_arg_intf.reference new file mode 100644 index 00000000000..2c5c65e3503 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/bad_arg_intf.reference @@ -0,0 +1,12 @@ +File "bad_arg_intf.mli", line 1: +Error: The argument module bad_arg_intf.mli + does not match the parameter signature monoid.cmi: + Values do not match: + val append : t -> t -> [ `Banana ] + is not included in + val append : t -> t -> t + The type t -> t -> [ `Banana ] is not compatible with the type + t -> t -> t + Type [ `Banana ] is not compatible with type t + File "monoid.mli", line 4, characters 0-24: Expected declaration + File "bad_arg_intf.mli", line 4, characters 0-34: Actual declaration diff --git a/ocaml/testsuite/tests/templates/basic/string_monoid.ml b/ocaml/testsuite/tests/templates/basic/string_monoid.ml new file mode 100644 index 00000000000..43385c451b3 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/string_monoid.ml @@ -0,0 +1,4 @@ +type t = string + +let empty = "" +let append = (^) diff --git a/ocaml/testsuite/tests/templates/basic/string_monoid.mli b/ocaml/testsuite/tests/templates/basic/string_monoid.mli new file mode 100644 index 00000000000..a915a865726 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/string_monoid.mli @@ -0,0 +1,4 @@ +type t = string + +val empty : t +val append : t -> t -> t diff --git a/ocaml/testsuite/tests/templates/basic/test.ml b/ocaml/testsuite/tests/templates/basic/test.ml index d6ed4eddc58..0b50f8f3775 100644 --- a/ocaml/testsuite/tests/templates/basic/test.ml +++ b/ocaml/testsuite/tests/templates/basic/test.ml @@ -1,13 +1,136 @@ (* TEST - readonly_files = "bad_ref_direct.ml bad_ref_direct.reference monoid.mli "; - setup-ocamlc.byte-build-env; - flags = "-as-parameter"; - module = "monoid.mli"; - ocamlc.byte; - module = "bad_ref_direct.ml"; - compiler_output = "bad_ref_direct.output"; - ocamlc_byte_exit_status = "2"; - ocamlc.byte; - compiler_reference = "bad_ref_direct.reference"; - check-ocamlc.byte-output; + readonly_files = "\ + bad_arg_impl.ml bad_arg_impl.reference \ + bad_arg_intf.mli bad_arg_intf.reference \ + bad_ref_direct.ml bad_ref_direct.reference \ + monoid.mli \ + string_monoid.ml string_monoid.mli \ + test_direct_access.ml test_direct_access.reference \ + "; + + { + setup-ocamlc.byte-build-env; + + flags = "-as-parameter"; + module = "monoid.mli"; + ocamlc.byte; + { + flags = ""; + module = "bad_ref_direct.ml"; + compiler_output = "bad_ref_direct.output"; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + + compiler_reference = "bad_ref_direct.reference"; + check-ocamlc.byte-output; + }{ + flags = "-as-argument-for Monoid"; + module = "bad_arg_impl.ml"; + compiler_output = "bad_arg_impl.output"; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + + compiler_reference = "bad_arg_impl.reference"; + check-ocamlc.byte-output; + }{ + flags = "-as-argument-for Monoid"; + module = "bad_arg_intf.mli"; + compiler_output = "bad_arg_intf.output"; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + + compiler_reference = "bad_arg_intf.reference"; + check-ocamlc.byte-output; + }{ + src = "string_monoid.ml"; + dst = "string_monoid_no_mli.ml"; + copy; + + flags = "-as-argument-for Monoid"; + module = "string_monoid_no_mli.ml string_monoid.mli string_monoid.ml"; + ocamlc.byte; + + flags = ""; + module = "test_direct_access.ml"; + ocamlc.byte; + + flags = ""; + program = "${test_build_directory}/test_direct_access.bc"; + module = ""; + all_modules = "\ + string_monoid.cmo \ + string_monoid_no_mli.cmo \ + test_direct_access.cmo \ + "; + ocamlc.byte; + + output = "test_direct_access.output"; + run; + + reference = "test_direct_access.reference"; + check-program-output; + } + }{ + setup-ocamlopt.byte-build-env; + + flags = "-as-parameter"; + module = "monoid.mli"; + ocamlopt.byte; + { + flags = ""; + module = "bad_ref_direct.ml"; + compiler_output = "bad_ref_direct.output"; + ocamlopt_byte_exit_status = "2"; + ocamlopt.byte; + + compiler_reference = "bad_ref_direct.reference"; + check-ocamlopt.byte-output; + }{ + flags = "-as-argument-for Monoid"; + module = "bad_arg_impl.ml"; + compiler_output = "bad_arg_impl.output"; + ocamlopt_byte_exit_status = "2"; + ocamlopt.byte; + + compiler_reference = "bad_arg_impl.reference"; + check-ocamlopt.byte-output; + }{ + flags = "-as-argument-for Monoid"; + module = "bad_arg_intf.mli"; + compiler_output = "bad_arg_intf.output"; + ocamlopt_byte_exit_status = "2"; + ocamlopt.byte; + + compiler_reference = "bad_arg_intf.reference"; + check-ocamlopt.byte-output; + }{ + src = "string_monoid.ml"; + dst = "string_monoid_no_mli.ml"; + copy; + + flags = "-as-argument-for Monoid"; + module = "string_monoid_no_mli.ml string_monoid.mli string_monoid.ml"; + ocamlopt.byte; + + flags = ""; + module = "test_direct_access.ml"; + ocamlopt.byte; + + flags = ""; + program = "${test_build_directory}/test_direct_access.exe"; + module = ""; + all_modules = "\ + string_monoid.cmx \ + string_monoid_no_mli.cmx \ + test_direct_access.cmx \ + "; + ocamlopt.byte; + + output = "test_direct_access.output"; + run; + + reference = "test_direct_access.reference"; + check-program-output; + } + } *) diff --git a/ocaml/testsuite/tests/templates/basic/test_direct_access.ml b/ocaml/testsuite/tests/templates/basic/test_direct_access.ml new file mode 100644 index 00000000000..01cb6358343 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/test_direct_access.ml @@ -0,0 +1,5 @@ +let () = + let open Printf in + printf "With .mli: %s\n" (String_monoid.append "Hello " "world!"); + printf "Without .mli: %s\n" (String_monoid_no_mli.append "Hello " "world!"); + () diff --git a/ocaml/testsuite/tests/templates/basic/test_direct_access.reference b/ocaml/testsuite/tests/templates/basic/test_direct_access.reference new file mode 100644 index 00000000000..bd0888b5798 --- /dev/null +++ b/ocaml/testsuite/tests/templates/basic/test_direct_access.reference @@ -0,0 +1,2 @@ +With .mli: Hello world! +Without .mli: Hello world! diff --git a/ocaml/typing/env.ml b/ocaml/typing/env.ml index c5cc6dec877..6c4e7e77189 100644 --- a/ocaml/typing/env.ml +++ b/ocaml/typing/env.ml @@ -1015,7 +1015,10 @@ let register_import_as_opaque modname = Persistent_env.register_import_as_opaque !persistent_env modname let is_parameter_unit modname = - Persistent_env.is_registered_parameter_import !persistent_env modname + Persistent_env.is_parameter_import !persistent_env modname + +let implemented_parameter modname = + Persistent_env.implemented_parameter !persistent_env modname let reset_declaration_caches () = Types.Uid.Tbl.clear !value_declarations; @@ -2639,6 +2642,9 @@ let read_signature modname filename ~add_binding = let mty = read_pers_mod modname filename ~add_binding in Subst.Lazy.force_signature mty +let register_parameter_import import = + Persistent_env.register_parameter_import !persistent_env import + let is_identchar_latin1 = function | 'A'..'Z' | 'a'..'z' | '_' | '\192'..'\214' | '\216'..'\246' | '\248'..'\255' | '\'' | '0'..'9' -> true diff --git a/ocaml/typing/env.mli b/ocaml/typing/env.mli index 1e1cfa42561..0f4a598a57e 100644 --- a/ocaml/typing/env.mli +++ b/ocaml/typing/env.mli @@ -477,6 +477,9 @@ val save_signature_with_imports: (* Arguments: signature, module name, module kind, file name, imported units with their CRCs. *) +(* Register a module as a parameter to this unit. *) +val register_parameter_import: Compilation_unit.Name.t -> unit + (* Return the CRC of the interface of the given compilation unit *) val crc_of_unit: Compilation_unit.Name.t -> Digest.t @@ -496,6 +499,11 @@ val register_import_as_opaque: Compilation_unit.Name.t -> unit -as-parameter *) val is_parameter_unit: Compilation_unit.Name.t -> bool +(* [implemented_parameter md] is the argument given to -as-argument-for when + [md] was compiled *) +val implemented_parameter: + Compilation_unit.Name.t -> Compilation_unit.Name.t option + (* Summaries -- compact representation of an environment, to be exported in debugging information. *) diff --git a/ocaml/typing/includemod.ml b/ocaml/typing/includemod.ml index 85d3e199139..8bf373bf204 100644 --- a/ocaml/typing/includemod.ml +++ b/ocaml/typing/includemod.ml @@ -100,8 +100,13 @@ module Error = struct {less_than:module_type_diff; greater_than: module_type_diff} + type compilation_unit_comparison = + | Implementation_vs_interface + | Argument_vs_parameter + type all = - | In_Compilation_unit of (string, signature_symptom) diff + | In_Compilation_unit of + compilation_unit_comparison * (string, signature_symptom) diff | In_Signature of signature_symptom | In_Include_functor_signature of signature_symptom | In_Module_type of module_type_diff @@ -1056,16 +1061,30 @@ let () = (* Check that an implementation of a compilation unit meets its interface. *) -let compunit env ~mark impl_name impl_sig intf_name intf_sig unit_shape = +let compunit0 + ~comparison env ~mark impl_name impl_sig intf_name intf_sig unit_shape = match signatures ~in_eq:false ~loc:(Location.in_file impl_name) env ~mark Subst.identity impl_sig intf_sig unit_shape with Result.Error reasons -> + let diff = Error.diff impl_name intf_name reasons in let cdiff = - Error.In_Compilation_unit(Error.diff impl_name intf_name reasons) in + Error.In_Compilation_unit(comparison, diff) in raise(Error(env, cdiff)) | Ok x -> x +let compunit = compunit0 ~comparison:Implementation_vs_interface + +(* Check that the interface of a compilation unit meets the interface of the + parameter it's declared to be an argument for using [-as-argument-for] *) + +let compunit_as_argument env arg_name arg_sig param_name param_sig = + let cc, _shape = + compunit0 env arg_name arg_sig param_name param_sig Shape.dummy_mod + ~comparison:Argument_vs_parameter ~mark:Mark_positive + in + cc + (* Functor diffing computation: The diffing computation uses the internal typing function *) diff --git a/ocaml/typing/includemod.mli b/ocaml/typing/includemod.mli index e96c39dd18d..6cb215cc6d5 100644 --- a/ocaml/typing/includemod.mli +++ b/ocaml/typing/includemod.mli @@ -109,9 +109,13 @@ module Error: sig | Incomparable of {less_than:module_type_diff; greater_than: module_type_diff} + type compilation_unit_comparison = + | Implementation_vs_interface + | Argument_vs_parameter type all = - | In_Compilation_unit of (string, signature_symptom) diff + | In_Compilation_unit of + compilation_unit_comparison * (string, signature_symptom) diff | In_Signature of signature_symptom | In_Include_functor_signature of signature_symptom | In_Module_type of module_type_diff @@ -181,6 +185,9 @@ val compunit: Env.t -> mark:mark -> string -> signature -> string -> signature -> Shape.t -> module_coercion * Shape.t +val compunit_as_argument: + Env.t -> string -> signature -> string -> signature -> module_coercion + val type_declarations: loc:Location.t -> Env.t -> mark:mark -> Ident.t -> type_declaration -> type_declaration -> unit diff --git a/ocaml/typing/includemod_errorprinter.ml b/ocaml/typing/includemod_errorprinter.ml index be5cc0be2ca..44994492cd7 100644 --- a/ocaml/typing/includemod_errorprinter.ml +++ b/ocaml/typing/includemod_errorprinter.ml @@ -710,6 +710,16 @@ let interface_mismatch ppf (diff: _ Err.diff) = "The implementation %s@ does not match the interface %s:@ " diff.got diff.expected +let parameter_mismatch ppf (diff: _ Err.diff) = + Format.fprintf ppf + "The argument module %s@ does not match the parameter signature %s:@ " + diff.got diff.expected + +let compilation_unit_mismatch comparison ppf diff = + match (comparison : Err.compilation_unit_comparison) with + | Implementation_vs_interface -> interface_mismatch ppf diff + | Argument_vs_parameter -> parameter_mismatch ppf diff + let core_module_type_symptom (x:Err.core_module_type_symptom) = match x with | Not_an_alias | Not_an_identifier | Abstract_module_type @@ -886,8 +896,10 @@ let module_type_subst ~env id diff = [main] let all env = function - | In_Compilation_unit diff -> - let first = Location.msg "%a" interface_mismatch diff in + | In_Compilation_unit (comparison, diff) -> + let first = + Location.msg "%a" (compilation_unit_mismatch comparison) diff + in signature ~expansion_token:true ~env ~before:[first] ~ctx:[] diff.symptom | In_Type_declaration (id,reason) -> [Location.msg "%t" (core env id reason)] diff --git a/ocaml/typing/persistent_env.ml b/ocaml/typing/persistent_env.ml index 68e48dc6856..2729eaeb3e1 100644 --- a/ocaml/typing/persistent_env.ml +++ b/ocaml/typing/persistent_env.ml @@ -35,7 +35,6 @@ type error = CU.t * filepath * CU.Prefix.t | Illegal_import_of_parameter of CU.Name.t * filepath | Not_compiled_as_parameter of CU.Name.t * filepath - | Cannot_implement_parameter of CU.Name.t * filepath exception Error of error let error err = raise (Error err) @@ -64,6 +63,7 @@ type can_load_cmis = (* Data relating directly to a .cmi *) type import = { imp_is_param : bool; + imp_arg_for : Compilation_unit.Name.t option; imp_impl : CU.t option; imp_sign : Subst.Lazy.signature; imp_filename : string; @@ -200,6 +200,11 @@ let check_consistency penv imp = let is_registered_parameter_import {param_imports; _} import = CU.Name.Set.mem import !param_imports +let is_parameter_import t import = + match find_import_info_in_cache t import with + | Some { imp_is_param; _ } -> imp_is_param + | None -> Misc.fatal_errorf "is_parameter_import %a" CU.Name.print import + let can_load_cmis penv = !(penv.can_load_cmis) let set_can_load_cmis penv setting = @@ -232,6 +237,9 @@ let save_import penv crc modname impl flags filename = Consistbl.check crc_units modname impl crc filename; add_import penv modname +(* Add an import to the hash table. Checks that we are allowed to access + this .cmi. *) + let acknowledge_import penv ~check modname pers_sig = let { Persistent_signature.filename; cmi; visibility } = pers_sig in let found_name = cmi.cmi_name in @@ -267,31 +275,15 @@ let acknowledge_import penv ~check modname pers_sig = | Normal _ -> false | Parameter -> true in - (* CR-someday lmaurer: Consider moving this check into - [acknowledge_pers_struct]. It makes more sense to flag these errors when - the identifier is in source, rather than, say, a signature we're reading - from a file, especially if it's our own .mli. *) - begin match is_param, is_registered_parameter_import penv modname with - | true, false -> - begin match CU.get_current () with - | Some current_unit when CU.Name.equal modname (CU.name current_unit) -> - error (Cannot_implement_parameter (modname, filename)) - | _ -> - error (Illegal_import_of_parameter(modname, filename)) - end - | false, true -> - error (Not_compiled_as_parameter(modname, filename)) - | true, true - | false, false -> () - end; - let impl = + let arg_for, impl = match kind with - | Normal { cmi_impl } -> Some cmi_impl - | Parameter -> None + | Normal { cmi_arg_for; cmi_impl } -> cmi_arg_for, Some cmi_impl + | Parameter -> None, None in let {imports; _} = penv in let import = { imp_is_param = is_param; + imp_arg_for = arg_for; imp_impl = impl; imp_sign = sign; imp_filename = filename; @@ -356,11 +348,24 @@ type 'a sig_reader = -> flags:Cmi_format.pers_flags list -> 'a +(* Add a persistent structure to the hash table and bind it in the [Env]. + Checks that OCaml source is allowed to refer to this module. *) + let acknowledge_pers_struct penv modname import val_of_pers_sig = let {persistent_structures; _} = penv in + let is_param = import.imp_is_param in let impl = import.imp_impl in let sign = import.imp_sign in + let filename = import.imp_filename in let flags = import.imp_flags in + begin match is_param, is_registered_parameter_import penv modname with + | true, false -> + error (Illegal_import_of_parameter(modname, filename)) + | false, true -> + error (Not_compiled_as_parameter(modname, filename)) + | true, true + | false, false -> () + end; let binding = make_binding penv impl in let address : address = match binding with @@ -440,7 +445,6 @@ let check_pers_struct ~allow_hidden penv f ~loc name = describe_prefix prefix | Illegal_import_of_parameter _ -> assert false | Not_compiled_as_parameter _ -> assert false - | Cannot_implement_parameter _ -> assert false in let warn = Warnings.No_cmi_file(name_as_string, Some msg) in Location.prerr_warning loc warn @@ -509,6 +513,11 @@ let is_imported {imported_units; _} s = let is_imported_opaque {imported_opaque_units; _} s = CU.Name.Set.mem s !imported_opaque_units +let implemented_parameter penv modname = + match find_import_info_in_cache penv modname with + | Some { imp_arg_for; _ } -> imp_arg_for + | None -> None + let make_cmi penv modname kind sign alerts = let flags = List.concat [ @@ -596,11 +605,6 @@ let report_error ppf = filename describe_prefix prefix "Can only access members of this library's package or a containing package" - | Cannot_implement_parameter(modname, _filename) -> - fprintf ppf - "@[The interface for %a@ was compiled with -as-parameter.@ \ - It cannot be implemented directly.@]" - CU.Name.print modname let () = Location.register_error_of_exn diff --git a/ocaml/typing/persistent_env.mli b/ocaml/typing/persistent_env.mli index 765d7a23e21..32d4086eb7e 100644 --- a/ocaml/typing/persistent_env.mli +++ b/ocaml/typing/persistent_env.mli @@ -34,7 +34,6 @@ type error = Compilation_unit.t * filepath * Compilation_unit.Prefix.t | Illegal_import_of_parameter of Compilation_unit.Name.t * filepath | Not_compiled_as_parameter of Compilation_unit.Name.t * filepath - | Cannot_implement_parameter of Compilation_unit.Name.t * filepath exception Error of error @@ -84,6 +83,8 @@ type 'a sig_reader = (* If [add_binding] is false, reads the signature from the .cmi but does not bind the module name in the environment. *) +(* CR-someday lmaurer: [add_binding] is apparently always false, including in the + [-instantiate] branch. We should remove this parameter. *) val read : 'a t -> 'a sig_reader -> Compilation_unit.Name.t -> filepath -> add_binding:bool -> Subst.Lazy.signature val find : allow_hidden:bool -> 'a t -> 'a sig_reader @@ -94,15 +95,14 @@ val find_in_cache : 'a t -> Compilation_unit.Name.t -> 'a option val check : allow_hidden:bool -> 'a t -> 'a sig_reader -> loc:Location.t -> Compilation_unit.Name.t -> unit -(* Lets it be known that the given module is a parameter and thus is expected - to have been compiled as such. It may or may not be a parameter to _this_ - module (see the forthcoming [register_exported_parameter]). Raises an - exception if the module has already been imported as a non-parameter. *) +(* Lets it be known that the given module is a parameter to this module and thus is + expected to have been compiled as such. Raises an exception if the module has already + been imported as a non-parameter. *) val register_parameter_import : 'a t -> Compilation_unit.Name.t -> unit -(* [is_registered_parameter_import penv md] checks if [md] has been passed to - [register_parameter_import penv] *) -val is_registered_parameter_import : 'a t -> Compilation_unit.Name.t -> bool +(* [is_parameter_import penv md] checks if [md] is a parameter. Raises a fatal + error if the module has not been imported. *) +val is_parameter_import : 'a t -> Compilation_unit.Name.t -> bool (* [looked_up penv md] checks if one has already tried to read the signature for [md] in the environment @@ -121,6 +121,11 @@ val is_imported_opaque : 'a t -> Compilation_unit.Name.t -> bool opaque module *) val register_import_as_opaque : 'a t -> Compilation_unit.Name.t -> unit +(* [implemented_parameter penv md] returns the argument to [-as-argument-for] + that [md] was compiled with. *) +val implemented_parameter : 'a t -> Compilation_unit.Name.t + -> Compilation_unit.Name.t option + val make_cmi : 'a t -> Compilation_unit.Name.t -> Cmi_format.kind diff --git a/ocaml/typing/typedtree.ml b/ocaml/typing/typedtree.ml index ff8bc5131d9..6ad7b86e9d5 100644 --- a/ocaml/typing/typedtree.ml +++ b/ocaml/typing/typedtree.ml @@ -798,10 +798,16 @@ and 'a class_infos = ci_attributes: attribute list; } +type argument_interface = { + ai_signature: Types.signature; + ai_coercion_from_primary: module_coercion; +} + type implementation = { structure: structure; coercion: module_coercion; signature: Types.signature; + argument_interface: argument_interface option; shape: Shape.t; } diff --git a/ocaml/typing/typedtree.mli b/ocaml/typing/typedtree.mli index bdd199496e1..f1fbb19f10d 100644 --- a/ocaml/typing/typedtree.mli +++ b/ocaml/typing/typedtree.mli @@ -1056,10 +1056,20 @@ and 'a class_infos = ci_attributes: attributes; } +type argument_interface = { + ai_signature: Types.signature; + ai_coercion_from_primary: module_coercion; +} +(** For a module [M] compiled with [-as-argument-for P] for some parameter + module [P], the signature of [P] along with the coercion from [M]'s + exported signature (the _primary interface_) to [P]'s signature (the + _argument interface_). *) + type implementation = { structure: structure; coercion: module_coercion; signature: Types.signature; + argument_interface: argument_interface option; shape: Shape.t; } (** A typechecked implementation including its module structure, its exported @@ -1070,6 +1080,10 @@ type implementation = { If there isn't one, the signature will be inferred from the module structure. + + If the module is compiled with [-as-argument-for] and is thus typechecked + against the .mli for a parameter in addition to its own .mli, it has an + additional signature stored in [argument_interface]. *) type item_declaration = diff --git a/ocaml/typing/typemod.ml b/ocaml/typing/typemod.ml index b1fbf9bd977..d3ca2059580 100644 --- a/ocaml/typing/typemod.ml +++ b/ocaml/typing/typemod.ml @@ -90,6 +90,14 @@ type error = | Strengthening_mismatch of Longident.t * Includemod.explanation | Cannot_pack_parameter | Cannot_compile_implementation_as_parameter + | Cannot_implement_parameter of Compilation_unit.Name.t * Misc.filepath + | Argument_for_non_parameter of Compilation_unit.Name.t * Misc.filepath + | Cannot_find_argument_type of Compilation_unit.Name.t + | Inconsistent_argument_types of { + new_arg_type : Compilation_unit.Name.t option; + old_arg_type : Compilation_unit.Name.t option; + old_source_file : Misc.filepath; + } exception Error of Location.t * Env.t * error exception Error_forward of Location.error @@ -3375,6 +3383,36 @@ let cms_register_toplevel_struct_attributes ~sourcefile ~uid ast = | { pstr_desc = Pstr_attribute attr; _ } -> Some attr | _ -> None) +let check_argument_type_if_given env sourcefile actual_sig arg_module_opt = + match arg_module_opt with + | None -> None + | Some arg_module -> + let arg_import = + (* This will soon be converting from one type to another *) + arg_module + in + (* CR lmaurer: This "look for known name in path" code is duplicated + all over the place. *) + let basename = arg_import |> Compilation_unit.Name.to_string in + let arg_filename = + try + Load_path.find_uncap (basename ^ ".cmi") + with Not_found -> + raise(Error(Location.none, Env.empty, + Cannot_find_argument_type arg_module)) in + let arg_sig = + Env.read_signature arg_module arg_filename ~add_binding:false in + if not (Env.is_parameter_unit arg_import) then + raise (Error (Location.none, env, + Argument_for_non_parameter (arg_module, arg_filename))); + let coercion = + Includemod.compunit_as_argument env sourcefile actual_sig + arg_filename arg_sig + in + Some { ai_signature = arg_sig; + ai_coercion_from_primary = coercion; + } + let type_implementation ~sourcefile outputprefix modulename initial_env ast = let error e = raise (Error (Location.in_file sourcefile, initial_env, e)) @@ -3408,18 +3446,24 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = { structure = str; coercion = Tcoerce_none; shape; - signature = simple_sg + signature = simple_sg; + argument_interface = None; } (* result is ignored by Compile.implementation *) end else begin if !Clflags.as_parameter then error Cannot_compile_implementation_as_parameter; + let arg_type = + !Clflags.as_argument_for + |> Option.map Compilation_unit.Name.of_string + in let sourceintf = Filename.remove_extension sourcefile ^ !Config.interface_suffix in if !Clflags.cmi_file <> None || Sys.file_exists sourceintf then begin + let import = Compilation_unit.name modulename in let intf_file = match !Clflags.cmi_file with | None -> - let basename = modulename |> Compilation_unit.name_as_string in + let basename = import |> Compilation_unit.Name.to_string in (try Load_path.find_uncap (basename ^ ".cmi") with Not_found -> @@ -3427,15 +3471,34 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = Interface_not_compiled sourceintf))) | Some cmi_file -> cmi_file in - let import = Compilation_unit.name modulename in let dclsig = Env.read_signature import intf_file ~add_binding:false in + if Env.is_parameter_unit import then + error (Cannot_implement_parameter (import, intf_file)); + let arg_type_from_cmi = Env.implemented_parameter import in + if not (Option.equal Compilation_unit.Name.equal + arg_type arg_type_from_cmi) then + error (Inconsistent_argument_types + { new_arg_type = arg_type; old_source_file = intf_file; + old_arg_type = arg_type_from_cmi }); let coercion, shape = Profile.record_call "check_sig" (fun () -> Includemod.compunit initial_env ~mark:Mark_positive sourcefile sg intf_file dclsig shape) in + (* Check the _mli_ against the argument type, since the mli determines + the visible type of the module and that's what needs to conform to + the argument type. + + This is somewhat redundant with the checking that was done when + compiling the .mli. However, this isn't just a boolean check - we + need to get the coercion out. An alternative would be to store the + coercion in the .cmi if we can sort out the dependency issues + ([Tcoerce_primitive] is a pain in particular). *) + let argument_interface = + check_argument_type_if_given initial_env intf_file dclsig arg_type + in Typecore.force_delayed_checks (); Typecore.optimise_allocations (); (* It is important to run these checks after the inclusion test above, @@ -3452,7 +3515,8 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = { structure = str; coercion; shape; - signature = dclsig + signature = dclsig; + argument_interface; } end else begin if !Clflags.as_parameter then @@ -3466,6 +3530,9 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = in check_nongen_signature finalenv simple_sg; normalize_signature simple_sg; + let argument_interface = + check_argument_type_if_given initial_env sourcefile simple_sg arg_type + in Typecore.force_delayed_checks (); Typecore.optimise_allocations (); (* See comment above. Here the target signature contains all @@ -3477,7 +3544,7 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = let alerts = Builtin_attributes.alerts_of_str ast in let name = Compilation_unit.name modulename in let kind = - Cmi_format.Normal { cmi_impl = modulename } + Cmi_format.Normal { cmi_impl = modulename; cmi_arg_for = arg_type } in let cmi = Profile.record_call "save_cmi" (fun () -> @@ -3495,7 +3562,8 @@ let type_implementation ~sourcefile outputprefix modulename initial_env ast = { structure = str; coercion; shape; - signature = simple_sg + signature = simple_sg; + argument_interface } end end @@ -3533,7 +3601,14 @@ let type_interface ~sourcefile modulename env ast = let uid = Shape.Uid.of_compilation_unit_id modulename in cms_register_toplevel_signature_attributes ~uid ~sourcefile ast end; - transl_signature env ast + let sg = transl_signature env ast in + let arg_type = + !Clflags.as_argument_for + |> Option.map Compilation_unit.Name.of_string + in + ignore (check_argument_type_if_given env sourcefile sg.sig_type arg_type + : Typedtree.argument_interface option); + sg (* "Packaging" of several compilation units into one unit having them as sub-modules. *) @@ -3633,8 +3708,12 @@ let package_units initial_env objfiles cmifile modulename = (Env.imports()) in (* Write packaged signature *) if not !Clflags.dont_write_files then begin + let cmi_arg_for = + (* Packs aren't supported as arguments *) + None + in let name = Compilation_unit.name modulename in - let kind = Cmi_format.Normal { cmi_impl = modulename } in + let kind = Cmi_format.Normal { cmi_impl = modulename; cmi_arg_for } in let cmi = Env.save_signature_with_imports ~alerts:Misc.Stdlib.String.Map.empty sg name kind (prefix ^ ".cmi") (Array.of_list imports) @@ -3880,6 +3959,36 @@ let report_error ~loc _env = function | Cannot_compile_implementation_as_parameter -> Location.errorf ~loc "Cannot compile an implementation with -as-parameter." + | Cannot_implement_parameter(modname, _filename) -> + Location.errorf ~loc + "@[The interface for %a@ was compiled with -as-parameter.@ \ + It cannot be implemented directly.@]" + Compilation_unit.Name.print modname + | Argument_for_non_parameter(param, path) -> + Location.errorf ~loc + "Interface %s@ found for module@ %a@ is not flagged as a parameter.@ \ + It cannot be the parameter type for this argument module." + path + Compilation_unit.Name.print param + | Inconsistent_argument_types + { new_arg_type; old_source_file; old_arg_type } -> + let pp_arg_type ppf arg_type = + match arg_type with + | None -> Format.fprintf ppf "without -as-argument-for" + | Some arg_type -> + Format.fprintf ppf "with -as-argument-for %a" + Compilation_unit.Name.print arg_type + in + Location.errorf ~loc + "Inconsistent usage of -as-argument-for. Interface@ %s@ was compiled \ + %a@ but this module is being compiled@ %a." + old_source_file + pp_arg_type old_arg_type + pp_arg_type new_arg_type + | Cannot_find_argument_type arg_type -> + Location.errorf ~loc + "Parameter module %a@ specified by -as-argument-for cannot be found." + Compilation_unit.Name.print arg_type let report_error env ~loc err = Printtyp.wrap_printing_env_error env diff --git a/ocaml/typing/typemod.mli b/ocaml/typing/typemod.mli index e7feb7dd6dd..adef32ad10b 100644 --- a/ocaml/typing/typemod.mli +++ b/ocaml/typing/typemod.mli @@ -42,11 +42,8 @@ val type_implementation: sourcefile:string -> string -> Compilation_unit.t -> Env.t -> Parsetree.structure -> Typedtree.implementation val type_interface: - sourcefile:string - -> Compilation_unit.t - -> Env.t - -> Parsetree.signature - -> Typedtree.signature + sourcefile:string -> Compilation_unit.t -> Env.t -> + Parsetree.signature -> Typedtree.signature val transl_signature: Env.t -> Parsetree.signature -> Typedtree.signature val check_nongen_signature: @@ -155,6 +152,14 @@ type error = | Strengthening_mismatch of Longident.t * Includemod.explanation | Cannot_pack_parameter | Cannot_compile_implementation_as_parameter + | Cannot_implement_parameter of Compilation_unit.Name.t * Misc.filepath + | Argument_for_non_parameter of Compilation_unit.Name.t * Misc.filepath + | Cannot_find_argument_type of Compilation_unit.Name.t + | Inconsistent_argument_types of { + new_arg_type: Compilation_unit.Name.t option; + old_arg_type: Compilation_unit.Name.t option; + old_source_file: Misc.filepath; + } exception Error of Location.t * Env.t * error exception Error_forward of Location.error diff --git a/ocaml/utils/clflags.ml b/ocaml/utils/clflags.ml index aa76847be00..9221fdc6ae3 100644 --- a/ocaml/utils/clflags.ml +++ b/ocaml/utils/clflags.ml @@ -116,6 +116,7 @@ and transparent_modules = ref false (* -trans-mod *) let unique_ids = ref true (* -d(no-)unique-ds *) let locations = ref true (* -d(no-)locations *) let as_parameter = ref false (* -as-parameter *) +and as_argument_for = ref None (* -as-argument-for *) let dump_source = ref false (* -dsource *) let dump_parsetree = ref false (* -dparsetree *) and dump_typedtree = ref false (* -dtypedtree *) diff --git a/ocaml/utils/clflags.mli b/ocaml/utils/clflags.mli index 2a2651aed31..82927b48ce7 100644 --- a/ocaml/utils/clflags.mli +++ b/ocaml/utils/clflags.mli @@ -127,6 +127,7 @@ val transparent_modules : bool ref val unique_ids : bool ref val locations : bool ref val as_parameter : bool ref +val as_argument_for : string option ref val dump_source : bool ref val dump_parsetree : bool ref val dump_typedtree : bool ref diff --git a/tools/flambda_backend_objinfo.ml b/tools/flambda_backend_objinfo.ml index 602a55fcbde..03dbcffbb9e 100644 --- a/tools/flambda_backend_objinfo.ml +++ b/tools/flambda_backend_objinfo.ml @@ -68,6 +68,10 @@ let print_impl_import import = let print_line name = printf "\t%s\n" name +let print_global_line glob = + (* Type will change soon for parameterised libraries *) + printf "\t%a\n" Compilation_unit.Name.output glob + let print_name_line cu = printf "\t%a\n" Compilation_unit.Name.output (Compilation_unit.name cu) @@ -113,6 +117,14 @@ let print_cmi_infos name crcs kind = | Parameter -> true in printf "Is parameter: %s\n" (if is_param then "YES" else "no"); + begin + match kind with + | Normal { cmi_arg_for = Some arg_for; _ } -> + printf "Argument for parameter:\n"; + print_global_line arg_for + | Normal _ | Parameter -> + () + end; printf "Interfaces imported:\n"; Array.iter print_intf_import crcs end