diff --git a/lib/core.js b/lib/core.js index 0f6bc6c049b..6e1f8d11987 100644 --- a/lib/core.js +++ b/lib/core.js @@ -2742,7 +2742,7 @@ declare var exports: {-[key: string]: mixed}; /* Opaque type for module reference magic strings */ declare opaque type $Flow$ModuleRef<+T>; - +declare opaque type $Flow$EsmModuleMarkerWrapperInModuleRef<+T>: T; /* Commonly available, shared between node and dom */ declare var console: { assert(condition: mixed, ...data: Array): void, diff --git a/prelude/prelude.js b/prelude/prelude.js index aec875f3d29..7b407ff5e7c 100644 --- a/prelude/prelude.js +++ b/prelude/prelude.js @@ -84,7 +84,7 @@ type AsyncIterator<+T> = $AsyncIterator; type AsyncIterable<+T> = $AsyncIterable; declare opaque type $Flow$ModuleRef<+T>; - +declare opaque type $Flow$EsmModuleMarkerWrapperInModuleRef<+T>: T; declare opaque type React$CreateElement; declare var module: { diff --git a/src/commands/commandUtils.ml b/src/commands/commandUtils.ml index d8f39685635..b022edf1e89 100644 --- a/src/commands/commandUtils.ml +++ b/src/commands/commandUtils.ml @@ -1416,6 +1416,8 @@ let make_options || FlowConfig.include_warnings flowconfig; opt_max_header_tokens = FlowConfig.max_header_tokens flowconfig; opt_haste_module_ref_prefix = FlowConfig.haste_module_ref_prefix flowconfig; + opt_haste_module_ref_prefix_standard_cjs_esm_interop = + FlowConfig.haste_module_ref_prefix_standard_cjs_esm_interop flowconfig; opt_haste_module_ref_prefix_LEGACY_INTEROP = FlowConfig.haste_module_ref_prefix_LEGACY_INTEROP flowconfig; opt_haste_name_reducers = FlowConfig.haste_name_reducers flowconfig; diff --git a/src/commands/config/flowConfig.ml b/src/commands/config/flowConfig.ml index b0c0fe02933..965fc09dd91 100644 --- a/src/commands/config/flowConfig.ml +++ b/src/commands/config/flowConfig.ml @@ -82,6 +82,7 @@ module Opts = struct gc_worker_space_overhead: int option; (** Gc.control's space_overhead *) gc_worker_window_size: int option; (** Gc.control's window_size *) haste_module_ref_prefix: string option; + haste_module_ref_prefix_standard_cjs_esm_interop: bool; haste_module_ref_prefix_LEGACY_INTEROP: string option; haste_name_reducers: (Str.regexp * string) list; haste_namespaces: string list; @@ -218,6 +219,7 @@ module Opts = struct gc_worker_space_overhead = None; gc_worker_window_size = None; haste_module_ref_prefix = None; + haste_module_ref_prefix_standard_cjs_esm_interop = false; haste_module_ref_prefix_LEGACY_INTEROP = None; haste_name_reducers = [(Str.regexp "^\\(.*/\\)?\\([a-zA-Z0-9$_.-]+\\)\\.js\\(\\.flow\\)?$", "\\2")]; @@ -1056,6 +1058,9 @@ module Opts = struct ("module.missing_module_generators", missing_module_generators_parser); ("module.system", module_system_parser); ("module.system.haste.module_ref_prefix", haste_module_ref_prefix_parser); + ( "module.system.haste.module_ref_prefix.standard_cjs_esm_interop", + boolean (fun opts v -> Ok { opts with haste_module_ref_prefix_standard_cjs_esm_interop = v }) + ); ( "module.system.haste.module_ref_prefix_LEGACY_INTEROP", haste_module_ref_prefix_LEGACY_INTEROP_parser ); @@ -1736,6 +1741,9 @@ let gc_worker_window_size c = c.options.Opts.gc_worker_window_size let haste_module_ref_prefix c = c.options.Opts.haste_module_ref_prefix +let haste_module_ref_prefix_standard_cjs_esm_interop c = + c.options.Opts.haste_module_ref_prefix_standard_cjs_esm_interop + let haste_module_ref_prefix_LEGACY_INTEROP c = c.options.Opts.haste_module_ref_prefix_LEGACY_INTEROP let haste_name_reducers c = c.options.Opts.haste_name_reducers diff --git a/src/commands/config/flowConfig.mli b/src/commands/config/flowConfig.mli index 7d885e5503a..26b62cb2fad 100644 --- a/src/commands/config/flowConfig.mli +++ b/src/commands/config/flowConfig.mli @@ -142,6 +142,8 @@ val gc_worker_window_size : config -> int option val haste_module_ref_prefix : config -> string option +val haste_module_ref_prefix_standard_cjs_esm_interop : config -> bool + val haste_module_ref_prefix_LEGACY_INTEROP : config -> string option val haste_name_reducers : config -> (Str.regexp * string) list diff --git a/src/common/options.ml b/src/common/options.ml index c01a2f1a4c1..3df55164c5b 100644 --- a/src/common/options.ml +++ b/src/common/options.ml @@ -105,6 +105,7 @@ type t = { opt_format: format; opt_gc_worker: gc_control; opt_haste_module_ref_prefix: string option; + opt_haste_module_ref_prefix_standard_cjs_esm_interop: bool; opt_haste_module_ref_prefix_LEGACY_INTEROP: string option; opt_haste_name_reducers: (Str.regexp * string) list; opt_haste_namespaces_options: Haste_namespaces.options; @@ -257,6 +258,9 @@ let gc_worker opts = opts.opt_gc_worker let haste_module_ref_prefix opts = opts.opt_haste_module_ref_prefix +let haste_module_ref_prefix_standard_cjs_esm_interop opts = + opts.opt_haste_module_ref_prefix_standard_cjs_esm_interop + let haste_module_ref_prefix_LEGACY_INTEROP opts = opts.opt_haste_module_ref_prefix_LEGACY_INTEROP let haste_name_reducers opts = opts.opt_haste_name_reducers diff --git a/src/flow_dot_js.ml b/src/flow_dot_js.ml index 95519f186b7..a3a794afb50 100644 --- a/src/flow_dot_js.ml +++ b/src/flow_dot_js.ml @@ -120,6 +120,7 @@ let stub_metadata ~root ~checked = facebook_fbt = None; facebook_module_interop = false; file_options = Files.default_options; + haste_module_ref_prefix_standard_cjs_esm_interop = false; ignore_non_literal_requires = false; max_literal_length = 100; max_workers = 0; diff --git a/src/services/code_action/__tests__/refactor_extract_utils_tests.ml b/src/services/code_action/__tests__/refactor_extract_utils_tests.ml index f75d4ff6c4c..7960adc6d86 100644 --- a/src/services/code_action/__tests__/refactor_extract_utils_tests.ml +++ b/src/services/code_action/__tests__/refactor_extract_utils_tests.ml @@ -53,6 +53,7 @@ let stub_metadata ~root ~checked = facebook_fbt = None; facebook_module_interop = false; file_options; + haste_module_ref_prefix_standard_cjs_esm_interop = false; ignore_non_literal_requires = false; max_literal_length = 100; max_workers = 0; diff --git a/src/typing/__tests__/type_hint_test.ml b/src/typing/__tests__/type_hint_test.ml index d5e6283b27f..65419c7c273 100644 --- a/src/typing/__tests__/type_hint_test.ml +++ b/src/typing/__tests__/type_hint_test.ml @@ -44,6 +44,7 @@ let metadata = facebook_fbt = None; facebook_module_interop = false; file_options = Files.default_options; + haste_module_ref_prefix_standard_cjs_esm_interop = false; ignore_non_literal_requires = false; max_literal_length = 100; max_workers = 0; diff --git a/src/typing/__tests__/typed_ast_test.ml b/src/typing/__tests__/typed_ast_test.ml index e655de9a9cc..109f05a488e 100644 --- a/src/typing/__tests__/typed_ast_test.ml +++ b/src/typing/__tests__/typed_ast_test.ml @@ -42,6 +42,7 @@ let metadata = facebook_fbt = None; facebook_module_interop = false; file_options = Files.default_options; + haste_module_ref_prefix_standard_cjs_esm_interop = false; ignore_non_literal_requires = false; max_literal_length = 100; max_workers = 0; diff --git a/src/typing/annotation_inference.ml b/src/typing/annotation_inference.ml index 522ed87d118..ec73108b263 100644 --- a/src/typing/annotation_inference.ml +++ b/src/typing/annotation_inference.ml @@ -105,6 +105,7 @@ module type S = sig Reason.t -> FlowSymbol.symbol -> is_strict:bool -> + standard_cjs_esm_interop:bool -> legacy_interop:bool -> Type.t @@ -672,13 +673,17 @@ module rec ConsGen : S = struct (******************) (* Module imports *) (******************) - | (ModuleT m, Annot_CJSRequireT { reason; namespace_symbol; is_strict; legacy_interop }) -> + | ( ModuleT m, + Annot_CJSRequireT + { reason; namespace_symbol; is_strict; standard_cjs_esm_interop; legacy_interop } + ) -> CJSRequireTKit.on_ModuleT cx ~reposition:(fun _ _ t -> t) ~reason ~module_symbol:namespace_symbol ~is_strict + ~standard_cjs_esm_interop ~legacy_interop m | (ModuleT m, Annot_ImportModuleNsT (reason, namespace_symbol, is_strict)) -> @@ -1390,8 +1395,14 @@ module rec ConsGen : S = struct in mk_lazy_tvar cx reason f - and cjs_require cx t reason namespace_symbol ~is_strict ~legacy_interop = - elab_t cx t (Annot_CJSRequireT { reason; namespace_symbol; is_strict; legacy_interop }) + and cjs_require cx t reason namespace_symbol ~is_strict ~standard_cjs_esm_interop ~legacy_interop + = + elab_t + cx + t + (Annot_CJSRequireT + { reason; namespace_symbol; is_strict; standard_cjs_esm_interop; legacy_interop } + ) and export_named cx reason export_kind value_exports_tmap type_exports_tmap t = elab_t cx t (Annot_ExportNamedT { reason; value_exports_tmap; type_exports_tmap; export_kind }) diff --git a/src/typing/context.ml b/src/typing/context.ml index 23eeb2df7f5..59d6801889e 100644 --- a/src/typing/context.ml +++ b/src/typing/context.ml @@ -53,6 +53,7 @@ type metadata = { facebook_fbt: string option; facebook_module_interop: bool; file_options: Files.options; + haste_module_ref_prefix_standard_cjs_esm_interop: bool; ignore_non_literal_requires: bool; max_literal_length: int; max_workers: int; @@ -280,6 +281,8 @@ let metadata_of_options options = facebook_fbt = Options.facebook_fbt options; facebook_module_interop = Options.facebook_module_interop options; file_options = Options.file_options options; + haste_module_ref_prefix_standard_cjs_esm_interop = + Options.haste_module_ref_prefix_standard_cjs_esm_interop options; ignore_non_literal_requires = Options.should_ignore_non_literal_requires options; max_literal_length = Options.max_literal_length options; max_workers = Options.max_workers options; @@ -634,6 +637,9 @@ let verbose cx = cx.metadata.verbose let slow_to_check_logging cx = cx.metadata.slow_to_check_logging +let haste_module_ref_prefix_standard_cjs_esm_interop cx = + cx.metadata.haste_module_ref_prefix_standard_cjs_esm_interop + let max_workers cx = cx.metadata.max_workers let missing_module_generators cx = cx.metadata.missing_module_generators diff --git a/src/typing/context.mli b/src/typing/context.mli index 04175e592f1..a551722abbd 100644 --- a/src/typing/context.mli +++ b/src/typing/context.mli @@ -97,6 +97,7 @@ type metadata = { facebook_fbt: string option; facebook_module_interop: bool; file_options: Files.options; + haste_module_ref_prefix_standard_cjs_esm_interop: bool; ignore_non_literal_requires: bool; max_literal_length: int; max_workers: int; @@ -297,6 +298,8 @@ val verbose : t -> Verbose.t option val slow_to_check_logging : t -> Slow_to_check_logging.t +val haste_module_ref_prefix_standard_cjs_esm_interop : t -> bool + val max_workers : t -> int val missing_module_generators : t -> (Str.regexp * string) list diff --git a/src/typing/flow_js_utils.ml b/src/typing/flow_js_utils.ml index 47ead9f8bc0..1d7d5f9a322 100644 --- a/src/typing/flow_js_utils.ml +++ b/src/typing/flow_js_utils.ml @@ -1518,7 +1518,15 @@ end module CJSRequireTKit = struct (* require('SomeModule') *) - let on_ModuleT cx ~reposition ~reason ~module_symbol ~is_strict ~legacy_interop module_ = + let on_ModuleT + cx + ~reposition + ~reason + ~module_symbol + ~is_strict + ~standard_cjs_esm_interop + ~legacy_interop + module_ = let { module_reason; module_export_types = exports; @@ -1534,11 +1542,6 @@ module CJSRequireTKit = struct we create below for non-CommonJS exports *) reposition cx (loc_of_reason reason) t | None -> - (* Use default export if option is enabled and module is not lib *) - let automatic_require_default = - (legacy_interop || Context.automatic_require_default cx) - && not (is_lib_reason_def module_reason) - in let value_exports_tmap = Context.find_exports cx exports.value_exports_tmap in let type_exports_tmap = Context.find_exports cx exports.type_exports_tmap in (* Convert ES module's named exports to an object *) @@ -1555,12 +1558,24 @@ module CJSRequireTKit = struct let types_tmap = Context.generate_property_map cx type_props in NamespaceT { namespace_symbol = module_symbol; values_type; types_tmap } in - if automatic_require_default then - match NameUtils.Map.find_opt (OrdinaryName "default") value_exports_tmap with - | Some { preferred_def_locs = _; name_loc = _; type_ } -> type_ - | _ -> mk_exports_namespace () + if standard_cjs_esm_interop then + lookup_builtin_typeapp + cx + reason + "$Flow$EsmModuleMarkerWrapperInModuleRef" + [mk_exports_namespace ()] else - mk_exports_namespace () + (* Use default export if option is enabled and module is not lib *) + let automatic_require_default = + (legacy_interop || Context.automatic_require_default cx) + && not (is_lib_reason_def module_reason) + in + if automatic_require_default then + match NameUtils.Map.find_opt (OrdinaryName "default") value_exports_tmap with + | Some { preferred_def_locs = _; name_loc = _; type_ } -> type_ + | _ -> mk_exports_namespace () + else + mk_exports_namespace () end (* import * as X from 'SomeModule'; *) diff --git a/src/typing/statement.ml b/src/typing/statement.ml index 4dbb98ddb61..2fc9b084a79 100644 --- a/src/typing/statement.ml +++ b/src/typing/statement.ml @@ -829,6 +829,7 @@ module Make cx (mk_reason (RModule mref) loc) ~namespace_symbol:(mk_module_symbol ~name:mref ~def_loc:loc) + ~standard_cjs_esm_interop:(Context.haste_module_ref_prefix_standard_cjs_esm_interop cx) ~legacy_interop module_t in @@ -3291,6 +3292,7 @@ module Make cx (mk_reason (RModule module_name) loc) ~namespace_symbol:(mk_module_symbol ~name:module_name ~def_loc:loc) + ~standard_cjs_esm_interop:false ~legacy_interop:false module_t | Error err -> @@ -3663,6 +3665,7 @@ module Make cx (mk_reason (RModule module_name) loc) ~namespace_symbol:(mk_module_symbol ~name:module_name ~def_loc:loc) + ~standard_cjs_esm_interop:false ~legacy_interop:false in (t, (args_loc, { ArgList.arguments = [Expression (expression cx lit_exp)]; comments })) @@ -3705,6 +3708,7 @@ module Make cx (mk_reason (RModule module_name) loc) ~namespace_symbol:(mk_module_symbol ~name:module_name ~def_loc:loc) + ~standard_cjs_esm_interop:false ~legacy_interop:false in (t, (args_loc, { ArgList.arguments = [Expression (expression cx lit_exp)]; comments })) diff --git a/src/typing/ty_normalizer_imports.ml b/src/typing/ty_normalizer_imports.ml index be670c4d70e..e05ccd81648 100644 --- a/src/typing/ty_normalizer_imports.ml +++ b/src/typing/ty_normalizer_imports.ml @@ -88,6 +88,7 @@ let add_require_bindings_from_exports_map cx loc source_name binding acc = cx reason ~namespace_symbol:(FlowSymbol.mk_module_symbol ~name:source_name ~def_loc:loc) + ~standard_cjs_esm_interop:false ~legacy_interop:false module_t in diff --git a/src/typing/type.ml b/src/typing/type.ml index 0aaf438d1b2..42377cf49c6 100644 --- a/src/typing/type.ml +++ b/src/typing/type.ml @@ -3370,6 +3370,7 @@ module AConstraint = struct reason: Reason.t; namespace_symbol: FlowSymbol.symbol; is_strict: bool; + standard_cjs_esm_interop: bool; legacy_interop: bool; } (* Exports *) diff --git a/src/typing/type_annotation.ml b/src/typing/type_annotation.ml index af93e66e6a2..c7121f1f36f 100644 --- a/src/typing/type_annotation.ml +++ b/src/typing/type_annotation.ml @@ -1061,6 +1061,7 @@ module Make (Statement : Statement_sig.S) : Type_annotation_sig.S = struct cx (mk_annot_reason (RModule value) loc) ~namespace_symbol:(FlowSymbol.mk_module_symbol ~name:value ~def_loc:loc) + ~standard_cjs_esm_interop:false ~legacy_interop:false remote_module_t ) diff --git a/src/typing/type_operation_utils.ml b/src/typing/type_operation_utils.ml index be9a0f03b92..8b74af75efa 100644 --- a/src/typing/type_operation_utils.ml +++ b/src/typing/type_operation_utils.ml @@ -268,7 +268,8 @@ module Import_export = struct ~remote_name:"default" ~local_name - let cjs_require_type cx reason ~namespace_symbol ~legacy_interop source_module_t = + let cjs_require_type + cx reason ~namespace_symbol ~standard_cjs_esm_interop ~legacy_interop source_module_t = let is_strict = Context.is_strict cx in match concretize_module_type cx reason source_module_t with | Ok m -> @@ -278,6 +279,7 @@ module Import_export = struct ~reason ~module_symbol:namespace_symbol ~is_strict + ~standard_cjs_esm_interop ~legacy_interop m | Error (lreason, any_source) -> AnyT (lreason, any_source) diff --git a/src/typing/type_operation_utils.mli b/src/typing/type_operation_utils.mli index f8a7de9abd8..a05cfebcfc6 100644 --- a/src/typing/type_operation_utils.mli +++ b/src/typing/type_operation_utils.mli @@ -77,7 +77,13 @@ module Import_export : sig ALoc.t option * Type.t val cjs_require_type : - Context.t -> reason -> namespace_symbol:symbol -> legacy_interop:bool -> Type.t -> Type.t + Context.t -> + reason -> + namespace_symbol:symbol -> + standard_cjs_esm_interop:bool -> + legacy_interop:bool -> + Type.t -> + Type.t end module Operators : sig diff --git a/src/typing/type_sig_merge.ml b/src/typing/type_sig_merge.ml index 8f9b392f1cd..eeaaf47828a 100644 --- a/src/typing/type_sig_merge.ml +++ b/src/typing/type_sig_merge.ml @@ -535,7 +535,8 @@ let rec merge ?(hooklike = false) ?(as_const = false) ?(const_decl = false) env let op = merge_op env file op in let t = eval file loc t op in make_hooklike file hooklike t - | Pack.Require { loc; index } -> require file loc index ~legacy_interop:false + | Pack.Require { loc; index } -> + require file loc index ~standard_cjs_esm_interop:false ~legacy_interop:false | Pack.ImportDynamic { loc; index } -> let (mref, _) = Module_refs.get file.dependencies index in let ns_reason = Reason.(mk_reason (RModule mref) loc) in @@ -544,7 +545,14 @@ let rec merge ?(hooklike = false) ?(as_const = false) ?(const_decl = false) env let t = Flow_js_utils.lookup_builtin_typeapp file.cx reason "Promise" [ns_t] in make_hooklike file hooklike t | Pack.ModuleRef { loc; index; legacy_interop } -> - let t = require file loc index ~legacy_interop in + let t = + require + file + loc + index + ~standard_cjs_esm_interop:(Context.haste_module_ref_prefix_standard_cjs_esm_interop file.cx) + ~legacy_interop + in let reason = Reason.(mk_reason (RCustom "module reference") loc) in let t = Flow_js_utils.lookup_builtin_typeapp file.cx reason "$Flow$ModuleRef" [t] in make_hooklike file hooklike t @@ -804,7 +812,14 @@ and merge_annot env file = function let module_t = Flow_js_utils.get_builtin_module file.cx ref loc in let reason = Reason.(mk_annot_reason (RModule ref) loc) in let symbol = FlowSymbol.mk_module_symbol ~name:ref ~def_loc:loc in - ConsGen.cjs_require file.cx module_t reason symbol ~is_strict:false ~legacy_interop:false + ConsGen.cjs_require + file.cx + module_t + reason + symbol + ~is_strict:false + ~standard_cjs_esm_interop:false + ~legacy_interop:false | Conditional { loc; distributive_tparam; infer_tparams; check_type; extends_type; true_type; false_type } -> diff --git a/tests/haste_module_ref_standard_cjs_esm_interop/.flowconfig b/tests/haste_module_ref_standard_cjs_esm_interop/.flowconfig new file mode 100644 index 00000000000..1ce6ff52d8a --- /dev/null +++ b/tests/haste_module_ref_standard_cjs_esm_interop/.flowconfig @@ -0,0 +1,5 @@ +[options] +all=true +module.system=haste +module.system.haste.module_ref_prefix=m# +module.system.haste.module_ref_prefix.standard_cjs_esm_interop=true diff --git a/tests/haste_module_ref_standard_cjs_esm_interop/esm.js b/tests/haste_module_ref_standard_cjs_esm_interop/esm.js new file mode 100644 index 00000000000..7fec2e45441 --- /dev/null +++ b/tests/haste_module_ref_standard_cjs_esm_interop/esm.js @@ -0,0 +1 @@ +declare export default string; diff --git a/tests/haste_module_ref_standard_cjs_esm_interop/haste_module_ref_standard_cjs_esm_interop.exp b/tests/haste_module_ref_standard_cjs_esm_interop/haste_module_ref_standard_cjs_esm_interop.exp new file mode 100644 index 00000000000..a5083b5eb5e --- /dev/null +++ b/tests/haste_module_ref_standard_cjs_esm_interop/haste_module_ref_standard_cjs_esm_interop.exp @@ -0,0 +1,38 @@ +Error ------------------------------------------------------------------------------------------------------ test.js:5:1 + +Cannot cast `extractedModuleInfo` to string because `$Flow$EsmModuleMarkerWrapperInModuleRef` [1] is incompatible with +string [2]. [incompatible-cast] + + test.js:5:1 + 5| extractedModuleInfo as string; // error; + ^^^^^^^^^^^^^^^^^^^ + +References: + /prelude.js:87:21 + 87| declare opaque type $Flow$EsmModuleMarkerWrapperInModuleRef<+T>: T; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] + test.js:5:24 + 5| extractedModuleInfo as string; // error; + ^^^^^^ [2] + + +Error ------------------------------------------------------------------------------------------------------ test.js:6:1 + +Cannot cast `extractedModuleInfo` to empty because `$Flow$EsmModuleMarkerWrapperInModuleRef` [1] is incompatible with +empty [2]. [incompatible-cast] + + test.js:6:1 + 6| extractedModuleInfo as empty; // error; + ^^^^^^^^^^^^^^^^^^^ + +References: + /prelude.js:87:21 + 87| declare opaque type $Flow$EsmModuleMarkerWrapperInModuleRef<+T>: T; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] + test.js:6:24 + 6| extractedModuleInfo as empty; // error; + ^^^^^ [2] + + + +Found 2 errors diff --git a/tests/haste_module_ref_standard_cjs_esm_interop/test.js b/tests/haste_module_ref_standard_cjs_esm_interop/test.js new file mode 100644 index 00000000000..62881d9b529 --- /dev/null +++ b/tests/haste_module_ref_standard_cjs_esm_interop/test.js @@ -0,0 +1,6 @@ +const moduleRef = 'm#esm'; +declare const extractedModuleInfo: typeof moduleRef extends $Flow$ModuleRef ? T : empty; +extractedModuleInfo as $Flow$EsmModuleMarkerWrapperInModuleRef; // ok +extractedModuleInfo as {+default: string}; // ok +extractedModuleInfo as string; // error; +extractedModuleInfo as empty; // error;