From 178f3520938af973247ff1a0ce7a953131005ba1 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:06:55 +0000 Subject: [PATCH 01/26] Remove dead Ctype diagnostic relays Drop Ctype.Tags and Ctype.Recursive_abbrev because both exceptions were declared and caught but had no raise site anywhere under compiler/. This also removes the unreachable Typetexp.Variant_tags and Typecore.Recursive_local_constraint wrappers that only existed to report those exceptions. Validation: rg found no remaining references to the removed constructors, and make completed successfully. --- compiler/ml/ctype.ml | 21 --------------------- compiler/ml/ctype.mli | 3 --- compiler/ml/typecore.ml | 18 +----------------- compiler/ml/typecore.mli | 1 - compiler/ml/typetexp.ml | 6 ------ compiler/ml/typetexp.mli | 1 - 6 files changed, 1 insertion(+), 49 deletions(-) diff --git a/compiler/ml/ctype.ml b/compiler/ml/ctype.ml index e15adf31b43..1291da7880c 100644 --- a/compiler/ml/ctype.ml +++ b/compiler/ml/ctype.ml @@ -57,19 +57,6 @@ type type_pairs = (type_expr * type_expr) list exception Unify of type_pairs -exception Tags of label * label - -let () = - Location.register_error_of_exn (function - | Tags (l, l') -> - Some - Location.( - errorf ~loc:(in_file !input_name) - "In this program,@ variant constructors@ #%s and #%s@ have the \ - same hash value.@ Change one of them." - l l') - | _ -> None) - type subtype_context = | Generic of {errorCode: string} | Coercion_target_variant_not_unboxed of { @@ -107,11 +94,6 @@ exception Cannot_expand exception Cannot_apply -exception Recursive_abbrev - -(* GADT: recursive abbrevs can appear as a result of local constraints *) -exception Unification_recursive_abbrev of type_pairs - (**** Type level management ****) let current_level = ref 0 @@ -2731,9 +2713,6 @@ let unify env ty1 ty2 = | Unify trace -> undo_compress snap; raise (Unify (expand_trace !env trace)) - | Recursive_abbrev -> - undo_compress snap; - raise (Unification_recursive_abbrev (expand_trace !env [(ty1, ty2)])) let unify_gadt ~newtype_level:lev (env : Env.t ref) ty1 ty2 = try diff --git a/compiler/ml/ctype.mli b/compiler/ml/ctype.mli index 6785374ac92..b671730402a 100644 --- a/compiler/ml/ctype.mli +++ b/compiler/ml/ctype.mli @@ -54,12 +54,9 @@ type subtype_context = type type_pairs = (type_expr * type_expr) list exception Unify of type_pairs -exception Tags of label * label exception Subtype of type_pairs * type_pairs * subtype_context option exception Cannot_expand exception Cannot_apply -exception Recursive_abbrev -exception Unification_recursive_abbrev of type_pairs val init_def : int -> unit (* Set the initial variable level *) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 4f0b3be38e4..d6b42771b96 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -69,7 +69,6 @@ type error = | Modules_not_allowed | Cannot_infer_signature | Not_a_packed_module of type_expr - | Recursive_local_constraint of (type_expr * type_expr) list | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string | Invalid_interval @@ -338,15 +337,11 @@ let check_optional_attr env ld optional loc = let unify_pat_types loc env ty ty' = try unify env ty ty' with | Unify trace -> raise (Error (loc, env, Pattern_type_clash trace)) - | Tags (l1, l2) -> - raise (Typetexp.Error (loc, env, Typetexp.Variant_tags (l1, l2))) (* unification inside type_exp and type_expect *) let unify_exp_types ~context loc env ty expected_ty = try unify env ty expected_ty with | Unify trace -> raise (Error (loc, env, Expr_type_clash {trace; context})) - | Tags (l1, l2) -> - raise (Typetexp.Error (loc, env, Typetexp.Variant_tags (l1, l2))) (* level at which to create the local type declarations *) let newtype_level = ref None @@ -363,10 +358,6 @@ let unify_pat_types_gadt loc env ty ty' = in try unify_gadt ~newtype_level env ty ty' with | Unify trace -> raise (Error (loc, !env, Pattern_type_clash trace)) - | Tags (l1, l2) -> - raise (Typetexp.Error (loc, !env, Typetexp.Variant_tags (l1, l2))) - | Unification_recursive_abbrev trace -> - raise (Error (loc, !env, Recursive_local_constraint trace)) (* Creating new conjunctive types is not allowed when typing patterns *) @@ -4520,8 +4511,7 @@ let type_expression ~context env sexp = (match sexp.pexp_desc with | Pexp_apply _ -> Some (return_type, FunctionCall) | _ -> Some (return_type, Other))) - | Tags _ -> - Location.prerr_warning sexp.pexp_loc (Bs_toplevel_expression_unit None)); + ); end_def (); if not (is_nonexpansive exp) then generalize_expansive env exp.exp_type; generalize exp.exp_type; @@ -4801,12 +4791,6 @@ let report_error env loc ppf error = | Not_a_packed_module ty -> fprintf ppf "This expression is packed module, but the expected type is@ %a" type_expr ty - | Recursive_local_constraint trace -> - (* modified *) - super_report_unification_error ppf env trace - (function - | ppf -> fprintf ppf "Recursive local constraint when unifying") - (function ppf -> fprintf ppf "with") | Unexpected_existential -> fprintf ppf "Unexpected existential" | Unqualified_gadt_pattern (tpath, name) -> fprintf ppf "@[The GADT constructor %s of type %a@ %s.@]" name Printtyp.path diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index 2a57356e73b..de89a516b76 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -102,7 +102,6 @@ type error = | Modules_not_allowed | Cannot_infer_signature | Not_a_packed_module of type_expr - | Recursive_local_constraint of (type_expr * type_expr) list | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string | Invalid_interval diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index 9867baac11a..2e735329db8 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -36,7 +36,6 @@ type error = | Present_has_no_type of string | Constructor_mismatch of type_expr * type_expr | Not_a_variant of type_expr - | Variant_tags of string * string | Invalid_variable_name of string | Cannot_quantify of string * type_expr | Multiple_constraints_on_type of Longident.t @@ -824,11 +823,6 @@ let report_error env ppf = function (* PR#7012: help the user that wrote 'Foo instead of `Foo *) Misc.did_you_mean ppf (fun () -> ["`" ^ s]) | _ -> ()) - | Variant_tags (lab1, lab2) -> - fprintf ppf "@[Variant tags %s@ and %s have the same hash value.@ %s@]" - (!Printtyp.print_res_poly_identifier lab1) - (!Printtyp.print_res_poly_identifier lab2) - "Change one of them." | Invalid_variable_name name -> fprintf ppf "The type variable name %s is not allowed in programs" name | Cannot_quantify (name, v) -> diff --git a/compiler/ml/typetexp.mli b/compiler/ml/typetexp.mli index 8f40096392f..cff8910482a 100644 --- a/compiler/ml/typetexp.mli +++ b/compiler/ml/typetexp.mli @@ -45,7 +45,6 @@ type error = | Present_has_no_type of string | Constructor_mismatch of type_expr * type_expr | Not_a_variant of type_expr - | Variant_tags of string * string | Invalid_variable_name of string | Cannot_quantify of string * type_expr | Multiple_constraints_on_type of Longident.t From 2671c120324533c01cdfc204f3908b9e5ca0e76e Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:07:56 +0000 Subject: [PATCH 02/26] Remove unused bs_syntaxerr conflict variant Drop Conflict_bs_bs_this_bs_meth because the constructor was only declared and printed; rg found no Bs_syntaxerr.err call or other construction site for it under compiler/. Validation: rg found no remaining references. The full rebuild is delayed by an unrelated coverage Dune process that currently holds _build/.lock. --- compiler/frontend/bs_syntaxerr.ml | 3 --- compiler/frontend/bs_syntaxerr.mli | 1 - 2 files changed, 4 deletions(-) diff --git a/compiler/frontend/bs_syntaxerr.ml b/compiler/frontend/bs_syntaxerr.ml index 668853bdf84..9cadcb58971 100644 --- a/compiler/frontend/bs_syntaxerr.ml +++ b/compiler/frontend/bs_syntaxerr.ml @@ -26,7 +26,6 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates - | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal @@ -65,8 +64,6 @@ let pp_error fmt err = | Not_supported_directive_in_bs_return -> "Not supported return directive" | Illegal_attribute -> "Illegal attributes" | Unsupported_predicates -> "unsupported predicates" - | Conflict_bs_bs_this_bs_meth -> - "%@this and %@bs can not be applied at the same time" | Duplicated_bs_deriving -> "duplicate @deriving attribute" | Conflict_attributes -> "conflicting attributes " | Expect_string_literal -> "expect string literal " diff --git a/compiler/frontend/bs_syntaxerr.mli b/compiler/frontend/bs_syntaxerr.mli index ecdaaaa0e6c..9c6a9ff9b5d 100644 --- a/compiler/frontend/bs_syntaxerr.mli +++ b/compiler/frontend/bs_syntaxerr.mli @@ -26,7 +26,6 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates - | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal From ffabe811026b7ee75e474a9657f5915f21485d5a Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:09:08 +0000 Subject: [PATCH 03/26] Remove unreachable typecore label diagnostics Drop Label_mismatch, Abstract_wrong_label, and Incoherent_label_order. Label_mismatch's pattern path was catching Ctype.Unify around unify_pat_types even though that helper already translates Unify to Pattern_type_clash. The expression path now uses the existing Expr_type_clash machinery if the defensive invariant ever fails. Abstract_wrong_label and Incoherent_label_order were defensive branches behind modern function and labeled-argument normalization; any fallback now uses the existing Too_many_arguments or Apply_wrong_label diagnostics. Validation: rg found no remaining references. A full rebuild will run once the unrelated coverage Dune process releases _build/.lock. --- compiler/ml/typecore.ml | 43 ++++++---------------------------------- compiler/ml/typecore.mli | 3 --- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index d6b42771b96..dded160fc4c 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -32,7 +32,6 @@ type error = expected: int; provided: int; } - | Label_mismatch of Longident.t * (type_expr * type_expr) list | Pattern_type_clash of (type_expr * type_expr) list | Or_pattern_type_clash of Ident.t * (type_expr * type_expr) list | Multiply_bound_variable of string @@ -61,10 +60,8 @@ type error = | Not_subtype of Ctype.type_pairs * Ctype.type_pairs * Ctype.subtype_context option | Too_many_arguments of bool * type_expr - | Abstract_wrong_label of arg_label * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t - | Incoherent_label_order | Less_general of string * (type_expr * type_expr) list | Modules_not_allowed | Cannot_infer_signature @@ -1528,10 +1525,7 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp begin_def (); let vars, ty_arg, ty_res = instance_label false label in if vars = [] then end_def (); - (try unify_pat_types loc !env ty_res record_ty - with Unify trace -> - raise - (Error (label_lid.loc, !env, Label_mismatch (label_lid.txt, trace)))); + unify_pat_types loc !env ty_res record_ty; type_pat sarg ty_arg (fun arg -> if vars <> [] then ( end_def (); @@ -3488,12 +3482,8 @@ and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l let ty_arg, ty_res = try filter_arrow ~env ~arity (instance env ty_expected) l with Unify _ -> ( - match expand_head env ty_expected with - | {desc = Tarrow _} as ty -> - raise (Error (loc, env, Abstract_wrong_label (l, ty))) - | _ -> - raise - (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun)))) + raise + (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun)))) in let ty_arg = if is_optional l then ( @@ -3575,9 +3565,8 @@ and type_label_exp ~call_context create env loc ty_expected (* Generalize label information *) generalize_structure ty_arg; generalize_structure ty_res); - (try unify env (instance_def ty_res) (instance env ty_expected) - with Unify trace -> - raise (Error (lid.loc, env, Label_mismatch (lid.txt, trace)))); + unify_exp_types ~context:None lid.loc env (instance_def ty_res) + (instance env ty_expected); (* Instantiate so that we can generalize internal nodes *) let ty_arg = instance_def ty_arg in if separate then ( @@ -3879,10 +3868,9 @@ and type_application ~context total_app env funct (sargs : sargs) : raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, funct.exp_type))) - else if not (has_label l1 ty_fun) then + else raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, ty_res))) - else raise (Error (funct.exp_loc, env, Incoherent_label_order)) | _ -> raise (Error @@ -4595,13 +4583,6 @@ let report_error env loc ppf error = (if expected == 1 then "argument" else "arguments") (if provided < expected then " only" else "") provided - | Label_mismatch (lid, trace) -> - (* modified *) - super_report_unification_error ppf env trace - (function - | ppf -> - fprintf ppf "The record field %a@ belongs to the type" longident lid) - (function ppf -> fprintf ppf "but is mixed here with fields of type") | Pattern_type_clash trace -> (* modified *) super_report_unification_error ppf env trace @@ -4756,14 +4737,6 @@ let report_error env loc ppf error = else ( fprintf ppf "@[This expression should not be a function,@ "; fprintf ppf "the expected type is@ %a@]" type_expr ty) - | Abstract_wrong_label (l, ty) -> - let label_mark = function - | Nolabel -> "but its first argument is not labelled" - | l -> - sprintf "but its first argument is labelled %s" (prefixed_label_name l) - in - fprintf ppf "@[@[<2>This function should have type@ %a@]@,%s@]" type_expr - ty (label_mark l) | Scoping_let_module (id, ty) -> fprintf ppf "This `let module' expression has type@ %a@ " type_expr ty; fprintf ppf @@ -4775,10 +4748,6 @@ let report_error env loc ppf error = type_expr ty | Not_a_variant_type lid -> fprintf ppf "The type %a@ is not a variant type" longident lid - | Incoherent_label_order -> - fprintf ppf "This labeled function is applied to arguments@ "; - fprintf ppf "in an order different from other calls.@ "; - fprintf ppf "This is only allowed when the real type is known." | Less_general (kind, trace) -> (* modified *) super_report_unification_error ppf env trace diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index de89a516b76..337c654bdab 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -65,7 +65,6 @@ type error = expected: int; provided: int; } - | Label_mismatch of Longident.t * (type_expr * type_expr) list | Pattern_type_clash of (type_expr * type_expr) list | Or_pattern_type_clash of Ident.t * (type_expr * type_expr) list | Multiply_bound_variable of string @@ -94,10 +93,8 @@ type error = | Not_subtype of Ctype.type_pairs * Ctype.type_pairs * Ctype.subtype_context option | Too_many_arguments of bool * type_expr - | Abstract_wrong_label of arg_label * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t - | Incoherent_label_order | Less_general of string * (type_expr * type_expr) list | Modules_not_allowed | Cannot_infer_signature From 1d061e8584b8a1f1303f73c2a69afad9c58b2f22 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:09:49 +0000 Subject: [PATCH 04/26] Remove parser-blocked typecore variants Drop Invalid_interval and Invalid_for_of_pattern from Typecore.error. The ReScript parser has no source construction site for Ppat_interval, and normalize_for_of_pattern reports destructuring for...of syntax before replacing it with Ppat_any. The old typer guards now use direct Location errors only as malformed-AST fallbacks. Validation: rg found no remaining references to the removed named variants. --- compiler/ml/typecore.ml | 19 +++++++++---------- compiler/ml/typecore.mli | 2 -- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index dded160fc4c..eb164f77d04 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -68,9 +68,7 @@ type error = | Not_a_packed_module of type_expr | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string - | Invalid_interval | Invalid_for_loop_index - | Invalid_for_of_pattern | No_value_clauses | Exception_pattern_below_toplevel | Inlined_record_escape @@ -1334,7 +1332,9 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp let p = {p with ppat_loc = loc} in type_pat ~explode:0 p expected_ty k (* TODO: record 'extra' to remember about interval *) - | Ppat_interval _ -> raise (Error (loc, !env, Invalid_interval)) + | Ppat_interval _ -> + Location.raise_errorf ~loc + "Only character intervals are supported in patterns." | Ppat_tuple spl -> assert (List.length spl >= 2); let spl_ann = List.map (fun p -> (p, newvar ())) spl in @@ -3102,7 +3102,9 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) val_kind = Val_reg; Types.val_loc = loc; } env ~check:(fun s -> Warnings.Unused_for_index s) - | _ -> raise (Error (param.ppat_loc, env, Invalid_for_of_pattern)) + | _ -> + Location.raise_errorf ~loc:param.ppat_loc + "Invalid for...of binding: only variables and _ are allowed." in let body = with_depth loop_depth (fun () -> @@ -3134,7 +3136,9 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) val_kind = Val_reg; Types.val_loc = loc; } env ~check:(fun s -> Warnings.Unused_for_index s) - | _ -> raise (Error (param.ppat_loc, env, Invalid_for_of_pattern)) + | _ -> + Location.raise_errorf ~loc:param.ppat_loc + "Invalid for...of binding: only variables and _ are allowed." in let body = with_depth loop_depth (fun () -> @@ -4764,13 +4768,8 @@ let report_error env loc ppf error = | Unqualified_gadt_pattern (tpath, name) -> fprintf ppf "@[The GADT constructor %s of type %a@ %s.@]" name Printtyp.path tpath "must be qualified in this pattern" - | Invalid_interval -> - fprintf ppf "@[Only character intervals are supported in patterns.@]" | Invalid_for_loop_index -> fprintf ppf "@[Invalid for-loop index: only variables and _ are allowed.@]" - | Invalid_for_of_pattern -> - fprintf ppf - "@[Invalid for...of binding: only variables and _ are allowed.@]" | No_value_clauses -> fprintf ppf "None of the patterns in this 'match' expression match values." | Exception_pattern_below_toplevel -> diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index 337c654bdab..cc63ed4fe72 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -101,9 +101,7 @@ type error = | Not_a_packed_module of type_expr | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string - | Invalid_interval | Invalid_for_loop_index - | Invalid_for_of_pattern | No_value_clauses | Exception_pattern_below_toplevel | Inlined_record_escape From e77abe3d234de76a322f03d61a923ac05210ea93 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:10:52 +0000 Subject: [PATCH 05/26] Remove parser-blocked typetexp variants Drop Unbound_type_constructor_2, Ill_typed_functor_application, and Apply_structure_as_functor from Typetexp.error. The first needs an inherited constructor whose body expands to a bare Tvar, which ReScript source cannot construct; the latter two only sit under Longident.Lapply, which the parser does not emit. The old guard sites now use direct Location errors as malformed-AST fallbacks. Validation: rg found no remaining references to the named variants. --- compiler/ml/typetexp.ml | 23 ++++++++++------------- compiler/ml/typetexp.mli | 3 --- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index 2e735329db8..12e1a36edf9 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -28,7 +28,6 @@ exception Already_bound type error = | Unbound_type_variable of string | Unbound_type_constructor of Longident.t - | Unbound_type_constructor_2 of Path.t | Type_arity_mismatch of Longident.t * int * int | Type_mismatch of (type_expr * type_expr) list | Alias_type_mismatch of (type_expr * type_expr) list @@ -45,10 +44,8 @@ type error = | Unbound_label of Longident.t * type_expr option | Unbound_module of Longident.t | Unbound_modtype of Longident.t - | Ill_typed_functor_application of Longident.t | Illegal_reference_to_recursive_module | Access_functor_as_structure of Longident.t - | Apply_structure_as_functor of Longident.t | Cannot_scrape_alias of Longident.t * Path.t | Opened_object of Path.t option | Not_an_object of type_expr @@ -89,7 +86,8 @@ let rec narrow_unbound_lid_error : 'a. _ -> _ -> _ -> _ -> 'a = let fmd = Env.find_module (Env.lookup_module ~load:true flid env) env in (match Env.scrape_alias env fmd.md_type with | Mty_signature _ -> - raise (Error (loc, env, Apply_structure_as_functor flid)) + Location.raise_errorf ~loc "The module %a is a structure, not a functor" + Printtyp.longident flid | Mty_alias (_, p) -> raise (Error (loc, env, Cannot_scrape_alias (flid, p))) | _ -> ()); @@ -98,7 +96,9 @@ let rec narrow_unbound_lid_error : 'a. _ -> _ -> _ -> _ -> 'a = match Env.scrape_alias env mmd.md_type with | Mty_alias (_, p) -> raise (Error (loc, env, Cannot_scrape_alias (mlid, p))) - | _ -> raise (Error (loc, env, Ill_typed_functor_application lid)))); + | _ -> + Location.raise_errorf ~loc "Ill-typed functor application %a" + Printtyp.longident lid)); raise (Error (loc, env, make_error lid)) let find_component (lookup : ?loc:_ -> _) make_error env loc lid = @@ -471,7 +471,9 @@ and transl_type_aux env policy styp = let row = Btype.row_repr row in row.row_fields | {desc = Tvar _}, Some (p, _) -> - raise (Error (sty.ptyp_loc, env, Unbound_type_constructor_2 p)) + Location.raise_errorf ~loc:sty.ptyp_loc + "The type constructor %a is not yet completely defined" + Printtyp.path p | _ -> raise (Error (sty.ptyp_loc, env, Not_a_variant ty)) in List.iter @@ -615,7 +617,8 @@ and transl_fields env policy o fields = iter_add tf; OTinherit cty | {desc = Tvar _}, Some p -> - raise (Error (sty.ptyp_loc, env, Unbound_type_constructor_2 p)) + Location.raise_errorf ~loc:sty.ptyp_loc + "The type constructor %a is not yet completely defined" Printtyp.path p | _ -> raise (Error (sty.ptyp_loc, env, Not_an_object t))) in let object_fields = List.map add_field fields in @@ -779,8 +782,6 @@ let report_error env ppf = function Format.fprintf ppf "If you wanted to write a recursive type, don't forget the `rec` in \ `type rec`@]" - | Unbound_type_constructor_2 p -> - fprintf ppf "The type constructor@ %a@ is not yet completely defined" path p | Type_arity_mismatch (lid, expected, provided) -> if expected == 0 then fprintf ppf @@ -954,14 +955,10 @@ let report_error env ppf = function | Unbound_modtype lid -> fprintf ppf "Unbound module type %a" longident lid; spellcheck ppf fold_modtypes env lid - | Ill_typed_functor_application lid -> - fprintf ppf "Ill-typed functor application %a" longident lid | Illegal_reference_to_recursive_module -> fprintf ppf "Illegal recursive module reference" | Access_functor_as_structure lid -> fprintf ppf "The module %a is a functor, not a structure" longident lid - | Apply_structure_as_functor lid -> - fprintf ppf "The module %a is a structure, not a functor" longident lid | Cannot_scrape_alias (lid, p) -> fprintf ppf "The module %a is an alias for module %a, which is missing" longident lid path p diff --git a/compiler/ml/typetexp.mli b/compiler/ml/typetexp.mli index cff8910482a..565535cb093 100644 --- a/compiler/ml/typetexp.mli +++ b/compiler/ml/typetexp.mli @@ -37,7 +37,6 @@ exception Already_bound type error = | Unbound_type_variable of string | Unbound_type_constructor of Longident.t - | Unbound_type_constructor_2 of Path.t | Type_arity_mismatch of Longident.t * int * int | Type_mismatch of (type_expr * type_expr) list | Alias_type_mismatch of (type_expr * type_expr) list @@ -54,10 +53,8 @@ type error = | Unbound_label of Longident.t * type_expr option | Unbound_module of Longident.t | Unbound_modtype of Longident.t - | Ill_typed_functor_application of Longident.t | Illegal_reference_to_recursive_module | Access_functor_as_structure of Longident.t - | Apply_structure_as_functor of Longident.t | Cannot_scrape_alias of Longident.t * Path.t | Opened_object of Path.t option | Not_an_object of type_expr From 6f4508d1469178a81c69b0f3a2f2a57dbd098ce5 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:12:00 +0000 Subject: [PATCH 06/26] Remove unreachable typedecl variants Drop Type_clash, Parameters_differ, Null_arity_external, Rebind_wrong_type, Bad_fixed_type, Varying_anonymous, and Val_in_structure from Typedecl.error. These guarded paths are rejected or normalized before the named diagnostics can fire in modern ReScript: recursive aliases are caught earlier, external declarations are encoded or rejected earlier, extension rebind result types unify by construction, fixed rows preserve their open row shape, anonymous type parameters are not parsed, and structure val declarations with empty prims do not reach the typer from source. The old guard sites now use direct Location errors as malformed-AST fallbacks. Validation: rg found no remaining references to the named variants. --- compiler/ml/typedecl.ml | 67 +++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 43 deletions(-) diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 0f44d4595f4..39eef5fed4f 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -34,22 +34,15 @@ type error = | Definition_mismatch of type_expr * Includecore.type_mismatch list | Constraint_failed of type_expr * type_expr | Inconsistent_constraint of Env.t * (type_expr * type_expr) list - | Type_clash of Env.t * (type_expr * type_expr) list - | Parameters_differ of Path.t * type_expr * type_expr - | Null_arity_external | Unbound_type_var of type_expr * type_declaration | Cannot_extend_private_type of Path.t | Not_extensible_type of Path.t | Extension_mismatch of Path.t * Includecore.type_mismatch list - | Rebind_wrong_type of Longident.t * Env.t * (type_expr * type_expr) list | Rebind_mismatch of Longident.t * Path.t * Path.t | Rebind_private of Longident.t | Bad_variance of int * (bool * bool * bool) * (bool * bool * bool) | Unavailable_type_constructor of Path.t - | Bad_fixed_type of string | Unbound_type_var_ext of type_expr * extension_constructor - | Varying_anonymous - | Val_in_structure | Invalid_attribute of string | Bad_immediate_attribute | Bad_unboxed_attribute of string @@ -122,7 +115,9 @@ let update_type temp_env env id loc = | Some ty -> ( let params = List.map (fun _ -> Ctype.newvar ()) decl.type_params in try Ctype.unify env (Ctype.newconstr path params) ty - with Ctype.Unify trace -> raise (Error (loc, Type_clash (env, trace)))) + with Ctype.Unify _ -> + Location.raise_errorf ~loc + "This type constructor expands to an incompatible type.") (* We use the Ctype.expand_head_opt version of expand_head to get access to the manifest type of private abbreviations. *) @@ -187,10 +182,12 @@ let set_fixed_row env loc p decl = tm.desc <- Tvariant {row with row_fixed = true}; if Btype.static_row row then Btype.newgenty Tnil else row.row_more | Tobject (ty, _) -> snd (Ctype.flatten_fields ty) - | _ -> raise (Error (loc, Bad_fixed_type "is not an object or variant")) + | _ -> + Location.raise_errorf ~loc + "This fixed type is not an object or variant" in if not (Btype.is_Tvar rv) then - raise (Error (loc, Bad_fixed_type "has no row variable")); + Location.raise_errorf ~loc "This fixed type has no row variable"; rv.desc <- Tconstr (p, decl.type_params, ref Mnil) (* Translate one type declaration *) @@ -982,11 +979,9 @@ let check_recursion env loc path decl to_check = | Tconstr (path', args', _) -> (if Path.same path path' then ( if not (Ctype.equal env false args args') then - raise - (Error - ( loc, - Parameters_differ (cpath, ty, Ctype.newconstr path args) - ))) + Location.raise_errorf ~loc + "In the definition of %s, recursive type parameters differ." + (Path.name cpath)) else if (* Attempt to expand a type abbreviation if: 1- [to_check path'] holds @@ -1260,7 +1255,9 @@ let compute_variance_gadt env check ((required, loc) as rloc) decl | fv :: fv2 -> (* fv1 @ fv2 = free_variables of other parameters *) if (c || n) && constrained (fv1 @ fv2) ty then - raise (Error (loc, Varying_anonymous)); + Location.raise_errorf ~loc + "In this GADT definition, the variance of some parameter \ + cannot be checked."; (fv :: fv1, fv2)) ([], fvl) tyl required in @@ -1649,8 +1646,11 @@ let transl_extension_constructor env type_path type_params typext_params priv else (Ctype.newconstr type_path typext_params, None) in (try Ctype.unify env cstr_res res - with Ctype.Unify trace -> - raise (Error (lid.loc, Rebind_wrong_type (lid.txt, env, trace)))); + with Ctype.Unify _ -> + Location.raise_errorf ~loc:lid.loc + "The constructor %a has a type that is incompatible with this \ + extension" + Printtyp.longident lid.txt); (* Remove "_" names from parameters used in the constructor *) (if not cdescr.cstr_generalized then let vars = Ctype.free_variables (Btype.newgenty (Ttuple args)) in @@ -1884,7 +1884,9 @@ let transl_value_decl env loc valdecl = Types.val_loc = loc; val_attributes = valdecl.pval_attributes; } - | [] -> raise (Error (valdecl.pval_loc, Val_in_structure)) + | [] -> + Location.raise_errorf ~loc:valdecl.pval_loc + "Value declarations are only allowed in signatures" | _ -> let arity, from_constructor = parse_arity env valdecl.pval_type ty in let prim = Primitive.parse_declaration valdecl ~arity ~from_constructor in @@ -1897,7 +1899,9 @@ let transl_value_decl env loc valdecl = && String.unsafe_get prim_native_name 1 = '\149')) && (prim.prim_name = "" || (prim.prim_name.[0] <> '%' && prim.prim_name.[0] <> '#')) - then raise (Error (valdecl.pval_type.ptyp_loc, Null_arity_external)); + then + Location.raise_errorf ~loc:valdecl.pval_type.ptyp_loc + "External identifiers must be functions"; { val_type = ty; val_kind = Val_prim prim; @@ -2153,22 +2157,11 @@ let report_error ppf = function fprintf ppf "@[%s@ @[Type@ %a@ should be an instance of@ %a@]@]" "Constraints are not satisfied in this type." Printtyp.type_expr ty Printtyp.type_expr ty' - | Parameters_differ (path, ty, ty') -> - Printtyp.reset_and_mark_loops ty; - Printtyp.mark_loops ty'; - fprintf ppf "@[In the definition of %s, type@ %a@ should be@ %a@]" - (Path.name path) Printtyp.type_expr ty Printtyp.type_expr ty' | Inconsistent_constraint (env, trace) -> fprintf ppf "The type constraints are not consistent.@."; Printtyp.report_unification_error ppf env trace (fun ppf -> fprintf ppf "Type") (fun ppf -> fprintf ppf "is not compatible with type") - | Type_clash (env, trace) -> - Printtyp.report_unification_error ppf env trace - (function - | ppf -> fprintf ppf "This type constructor expands to type") - (function ppf -> fprintf ppf "but is used here with type") - | Null_arity_external -> fprintf ppf "External identifiers must be functions" | Unbound_type_var (ty, decl) -> ( fprintf ppf "A type variable is unbound in this type declaration"; let ty = Ctype.repr ty in @@ -2204,12 +2197,6 @@ let report_error ppf = function "does not match the definition of type" (Path.name path) (Includecore.report_type_mismatch "the type" "this extension" "definition") errs - | Rebind_wrong_type (lid, env, trace) -> - Printtyp.report_unification_error ppf env trace - (function - | ppf -> - fprintf ppf "The constructor %a@ has type" Printtyp.longident lid) - (function ppf -> fprintf ppf "but was expected to be of type") | Rebind_mismatch (lid, p, p') -> fprintf ppf "@[%s@ %a@ %s@ %s@ %s@ %s@ %s@]" "The constructor" Printtyp.longident lid "extends type" (Path.name p) @@ -2256,12 +2243,6 @@ let report_error ppf = function (variance v1) | Unavailable_type_constructor p -> fprintf ppf "The definition of type %a@ is unavailable" Printtyp.path p - | Bad_fixed_type r -> fprintf ppf "This fixed type %s" r - | Varying_anonymous -> - fprintf ppf "@[%s@ %s@ %s@]" "In this GADT definition," - "the variance of some parameter" "cannot be checked" - | Val_in_structure -> - fprintf ppf "Value declarations are only allowed in signatures" | Bad_immediate_attribute -> fprintf ppf "@[%s@ %s@]" "Types marked with the immediate attribute must be" "non-pointer types like int or bool" From e7206b08cd436f49842c0279e819585b43182a13 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:12:57 +0000 Subject: [PATCH 07/26] Remove unreachable typemod variants Drop Cannot_eliminate_dependency, With_makes_applicative_functor_ill_typed, With_cannot_remove_constrained_type, and Scoping_pack from Typemod.error. The dead reports were guarded by paths the current module pipeline does not reach: nondep_supertype falls back instead of raising Not_found, applicative Papply constraints are not parsed from source, destructive type substitutions only get fresh type variables, and the package scoping mismatch is a defensive fallback. The old guard sites now use direct Location errors as malformed-AST fallbacks. Validation: rg found no remaining references to the named variants. --- compiler/ml/typemod.ml | 54 +++++++++++++++-------------------------- compiler/ml/typemod.mli | 5 ---- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/compiler/ml/typemod.ml b/compiler/ml/typemod.ml index 0932c62f347..7fec8d74db0 100644 --- a/compiler/ml/typemod.ml +++ b/compiler/ml/typemod.ml @@ -24,15 +24,11 @@ open Format type error = | Cannot_apply of module_type | Not_included of Includemod.error list - | Cannot_eliminate_dependency of module_type | Signature_expected | Structure_expected of module_type | With_no_component of Longident.t | With_mismatch of Longident.t * Includemod.error list - | With_makes_applicative_functor_ill_typed of - Longident.t * Path.t * Includemod.error list | With_changes_module_alias of Longident.t * Ident.t * Path.t - | With_cannot_remove_constrained_type | Repeated_name of string * string * Warnings.loc | Non_generalizable of type_expr | Non_generalizable_module of module_type @@ -40,7 +36,6 @@ type error = | Not_allowed_in_functor_body | Not_a_packed_module of type_expr | Incomplete_packed_module of type_expr - | Scoping_pack of Longident.t * type_expr | Recursive_module_require_explicit_type | Apply_generative | Cannot_scrape_alias of Path.t @@ -251,12 +246,12 @@ let check_usage_of_path_of_substituted_item paths env signature ~loc ~lid = let env = !env in try retype_applicative_functor_type ~loc env funct arg with Includemod.Error explanation -> - raise - (Error - ( loc, - env, - With_makes_applicative_functor_ill_typed - (lid.txt, referenced_path, explanation) )))); + Location.raise_errorf ~loc + "@[@[This `with' constraint on %a makes the \ + applicative functor type %s ill-typed in the constrained \ + signature:@]@ %a@]" + Printtyp.longident lid.txt (Path.name referenced_path) + Includemod.report_error explanation)); } in iterator.Btype.it_signature iterator signature; @@ -439,8 +434,10 @@ let merge_constraint initial_env loc sg constr = in let params = tdecl.typ_type.type_params in if params_are_constrained params then - raise - (Error (loc, initial_env, With_cannot_remove_constrained_type)); + Location.raise_errorf ~loc + "@[Destructive substitutions are not supported for \ + constrained types (other than when replacing a type \ + constructor with a type constructor with the same arguments).@]"; fun s path -> Subst.add_type_function path ~params ~body s in let sub = List.fold_left how_to_extend_subst Subst.identity !real_ids in @@ -1330,9 +1327,11 @@ and type_module_aux ~alias sttn funct_body anchor env smod = (Env.add_module ~arg:true param arg.mod_type env) param mty_res with Not_found -> - raise - (Error - (smod.pmod_loc, env, Cannot_eliminate_dependency mty_functor)) + Location.raise_errorf ~loc:smod.pmod_loc + "@[This functor has type@ %a@ The parameter cannot be \ + eliminated in the result type.@ Bind the argument to a \ + module identifier.@]" + Printtyp.modtype mty_functor ) in rm @@ -1714,7 +1713,10 @@ let type_package env m p nl = (fun n ty -> try Ctype.unify env ty (Ctype.newvar ()) with Ctype.Unify _ -> - raise (Error (m.pmod_loc, env, Scoping_pack (n, ty)))) + Location.raise_errorf ~loc:m.pmod_loc + "@[The type %a in this module cannot be exported.@ Its type \ + contains local dependencies:@ %a@]" + Printtyp.longident n Printtyp.type_expr ty) nl tl'; (wrap_constraint env modl mty Tmodtype_implicit, tl') @@ -1818,11 +1820,6 @@ let report_error ppf = function fprintf ppf "@[This module is not a functor; it has type@ %a@]" modtype mty | Not_included errs -> fprintf ppf "@[Signature mismatch:@ %a@]" Includemod.report_error errs - | Cannot_eliminate_dependency mty -> - fprintf ppf - "@[This functor has type@ %a@ The parameter cannot be eliminated in the \ - result type.@ Bind the argument to a module identifier.@]" - modtype mty | Signature_expected -> fprintf ppf "This module type is not a signature" | Structure_expected mty -> fprintf ppf "@[This module is not a structure; it has type@ %a" modtype mty @@ -1835,21 +1832,11 @@ let report_error ppf = function "@[@[In this `with' constraint, the new definition of %a@ does not \ match its original definition@ in the constrained signature:@]@ %a@]" longident lid Includemod.report_error explanation - | With_makes_applicative_functor_ill_typed (lid, path, explanation) -> - fprintf ppf - "@[@[This `with' constraint on %a makes the applicative functor @ \ - type %s ill-typed in the constrained signature:@]@ %a@]" - longident lid (Path.name path) Includemod.report_error explanation | With_changes_module_alias (lid, id, path) -> fprintf ppf "@[@[This `with' constraint on %a changes %s, which is aliased @ in \ the constrained signature (as %s)@].@]" longident lid (Path.name path) (Ident.name id) - | With_cannot_remove_constrained_type -> - fprintf ppf - "@[Destructive substitutions are not supported for constrained @ \ - types (other than when replacing a type constructor with @ a type \ - constructor with the same arguments).@]" | Repeated_name (kind, name, repeated_loc) -> fprintf ppf "@[Multiple definition of the %s name %s @ at @{%a@}@ @ Names must \ @@ -1887,9 +1874,6 @@ let report_error ppf = function | Incomplete_packed_module ty -> fprintf ppf "The type of this packed module contains variables:@ %a" type_expr ty - | Scoping_pack (lid, ty) -> - fprintf ppf "The type %a in this module cannot be exported.@ " longident lid; - fprintf ppf "Its type contains local dependencies:@ %a" type_expr ty | Recursive_module_require_explicit_type -> fprintf ppf "Recursive modules require an explicit module type." | Apply_generative -> diff --git a/compiler/ml/typemod.mli b/compiler/ml/typemod.mli index 0f36cf6afd8..8eea12e2f6b 100644 --- a/compiler/ml/typemod.mli +++ b/compiler/ml/typemod.mli @@ -64,15 +64,11 @@ val save_signature : type error = | Cannot_apply of module_type | Not_included of Includemod.error list - | Cannot_eliminate_dependency of module_type | Signature_expected | Structure_expected of module_type | With_no_component of Longident.t | With_mismatch of Longident.t * Includemod.error list - | With_makes_applicative_functor_ill_typed of - Longident.t * Path.t * Includemod.error list | With_changes_module_alias of Longident.t * Ident.t * Path.t - | With_cannot_remove_constrained_type | Repeated_name of string * string * Warnings.loc | Non_generalizable of type_expr | Non_generalizable_module of module_type @@ -80,7 +76,6 @@ type error = | Not_allowed_in_functor_body | Not_a_packed_module of type_expr | Incomplete_packed_module of type_expr - | Scoping_pack of Longident.t * type_expr | Recursive_module_require_explicit_type | Apply_generative | Cannot_scrape_alias of Path.t From 403ae1791c8a0a530361da5b4ec8024feca012c1 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:13:32 +0000 Subject: [PATCH 08/26] Remove parser-blocked frontend syntax variants Drop Unhandled_poly_type and Misplaced_label_syntax from Bs_syntaxerr.error. Inline polytypes in external arrow chains and labeled arguments to the legacy ->/#=/## rewrites are not emitted by the ReScript parser. The old guard sites now raise direct Location errors as malformed-AST fallbacks. Validation: rg found no remaining references to the named variants. --- compiler/frontend/ast_core_type.ml | 3 +-- compiler/frontend/bs_syntaxerr.ml | 10 ++-------- compiler/frontend/bs_syntaxerr.mli | 2 -- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/compiler/frontend/ast_core_type.ml b/compiler/frontend/ast_core_type.ml index b93c4586bf5..3f056c2bb4f 100644 --- a/compiler/frontend/ast_core_type.ml +++ b/compiler/frontend/ast_core_type.ml @@ -137,8 +137,7 @@ let list_of_arrow (ty : t) : t * Parsetree.arg list = | Ptyp_arrow {arg; ret; arity} when arity = None || acc = [] -> aux ret (arg :: acc) | Ptyp_poly (_, ty) -> - (* should not happen? *) - Bs_syntaxerr.err ty.ptyp_loc Unhandled_poly_type + Location.raise_errorf ~loc:ty.ptyp_loc "Unhandled poly type" | _ -> (ty, List.rev acc) in aux ty [] diff --git a/compiler/frontend/bs_syntaxerr.ml b/compiler/frontend/bs_syntaxerr.ml index 9cadcb58971..7ef491e6f9d 100644 --- a/compiler/frontend/bs_syntaxerr.ml +++ b/compiler/frontend/bs_syntaxerr.ml @@ -31,7 +31,6 @@ type error = | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal - | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type @@ -43,7 +42,6 @@ type error = *) | Not_supported_directive_in_bs_return | Expect_opt_in_bs_return_to_opt - | Misplaced_label_syntax | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature @@ -52,10 +50,6 @@ type error = let pp_error fmt err = Format.pp_print_string fmt (match err with - | Misplaced_label_syntax -> "Label syntax is not support in this position" - (* - let fn x = ((##) x ~hi) ~lo:1 ~hi:2 - *) | Optional_in_uncurried_bs_attribute -> "Uncurried function doesn't support optional arguments yet" | Expect_opt_in_bs_return_to_opt -> @@ -70,7 +64,6 @@ let pp_error fmt err = | Expect_int_literal -> "expect int literal " | Expect_int_or_string_or_json_literal -> "expect int, string literal or json literal {json|text here|json} " - | Unhandled_poly_type -> "Unhandled poly type" | Invalid_underscore_type_in_external -> "_ is not allowed in combination with external optional type" | Invalid_bs_string_type -> "Not a valid type for %@string" @@ -110,4 +103,5 @@ let optional_err loc (lbl : Asttypes.arg_label) = | _ -> () let err_if_label loc (lbl : Asttypes.arg_label) = - if lbl <> Nolabel then raise (Error (loc, Misplaced_label_syntax)) + if lbl <> Nolabel then + Location.raise_errorf ~loc "Label syntax is not supported in this position" diff --git a/compiler/frontend/bs_syntaxerr.mli b/compiler/frontend/bs_syntaxerr.mli index 9c6a9ff9b5d..c174245fb1b 100644 --- a/compiler/frontend/bs_syntaxerr.mli +++ b/compiler/frontend/bs_syntaxerr.mli @@ -31,7 +31,6 @@ type error = | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal - | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type @@ -43,7 +42,6 @@ type error = *) | Not_supported_directive_in_bs_return | Expect_opt_in_bs_return_to_opt - | Misplaced_label_syntax | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature From 02fef7c0b23439eae4a7980b4e4dd69f94f1b078 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:14:04 +0000 Subject: [PATCH 09/26] Remove parser-blocked env value-name variant Drop Illegal_value_name from Env.error. The ReScript parser does not emit value identifiers named -> or containing #, so the named environment error is unreachable from source. The check_value_name guard now raises a direct Location error for malformed PPX-forged identifiers. Validation: rg found no remaining references to the named variant. --- compiler/ml/env.ml | 14 +++++--------- compiler/ml/env.mli | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/compiler/ml/env.ml b/compiler/ml/env.ml index 970634be03d..b6e23e8def8 100644 --- a/compiler/ml/env.ml +++ b/compiler/ml/env.ml @@ -58,7 +58,6 @@ type error = | Illegal_renaming of string * string * string | Inconsistent_import of string * string * string | Missing_module of Location.t * Path.t * Path.t - | Illegal_value_name of Location.t * string exception Error of error @@ -730,7 +729,6 @@ let check_pers_struct name = Location.print_filename filename ps_name name | Inconsistent_import _ -> assert false | Missing_module _ -> assert false - | Illegal_value_name _ -> assert false in let warn = Warnings.No_cmi_file (name, Some msg) in Location.prerr_warning Location.none warn @@ -1619,10 +1617,12 @@ and check_value_name name loc = (* Note: we could also check here general validity of the identifier, to protect against bad identifiers forged by -pp or -ppx preprocessors. *) - if name = "->" then raise (Error (Illegal_value_name (loc, name))) + if name = "->" then + Location.raise_errorf ~loc "'%s' is not a valid value identifier." name else if String.length name > 0 && name.[0] = '#' then for i = 1 to String.length name - 1 do - if name.[i] = '#' then raise (Error (Illegal_value_name (loc, name))) + if name.[i] = '#' then + Location.raise_errorf ~loc "'%s' is not a valid value identifier." name done and store_value ?check id decl env = @@ -2129,13 +2129,9 @@ let report_error ppf = function fprintf ppf "@]@ @[%s@ %s@ %s.@]@]" "The compiled interface for module" (Ident.name (Path.head path2)) "was not found" - | Illegal_value_name (_loc, name) -> - fprintf ppf "'%s' is not a valid value identifier." name - let () = Location.register_error_of_exn (function - | Error ((Missing_module (loc, _, _) | Illegal_value_name (loc, _)) as err) - when loc <> Location.none -> + | Error (Missing_module (loc, _, _) as err) when loc <> Location.none -> Some (Location.error_of_printer loc report_error err) | Error err -> Some (Location.error_of_printer_file report_error err) | _ -> None) diff --git a/compiler/ml/env.mli b/compiler/ml/env.mli index 48eaba1c10d..90946f94e36 100644 --- a/compiler/ml/env.mli +++ b/compiler/ml/env.mli @@ -236,7 +236,6 @@ type error = | Illegal_renaming of string * string * string | Inconsistent_import of string * string * string | Missing_module of Location.t * Path.t * Path.t - | Illegal_value_name of Location.t * string exception Error of error From 6a1947f2082b3697b24e0191c8699c070ce1b519 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:15:54 +0000 Subject: [PATCH 10/26] Remove leftover typecore label helper Drop has_label after removing the unreachable Incoherent_label_order path. The full make checkpoint caught it as an unused helper under the repository warning policy. Validation: make -j2 completed successfully after this cleanup. --- compiler/ml/typecore.ml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index eb164f77d04..8e6da258c69 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -3734,10 +3734,6 @@ and type_application ~context total_app env funct (sargs : sargs) : newty2 lv (Tarrow ({lbl = l; typ = ty}, ty_fun, Cok, None))) ty_fun omitted in - let has_label l ty_fun = - let ls, tvar = list_labels env ty_fun in - tvar || List.mem l ls - in let ignored = ref [] in let force_tvar = let t = funct.exp_type in From d70ef8fb80ddce024836518dae899abb4fdc11c7 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:21:37 +0000 Subject: [PATCH 11/26] Remove dead warning constructors Drop warning constructors that have no live raise path: old OCaml lexer/class/docstring warnings, unused BuckleScript warnings, Statement_type, Unerasable_optional_argument, and Bs_uninterpreted_delimiters. Statement_type was guarded by a check_application_result argument that is only ever false. Unerasable_optional_argument was disabled around its only check. Bs_uninterpreted_delimiters only covered the old unprocessed js delimiter warning path, which modern string handling no longer uses. The warning numbers remain holes; no existing warning numbers were reused. Validation: rg found no remaining references to the removed warning constructors, and make -j2 completed successfully. --- compiler/common/bs_warnings.ml | 3 - compiler/common/bs_warnings.mli | 2 - compiler/ext/warnings.ml | 104 ------------------- compiler/ext/warnings.mli | 14 --- compiler/frontend/ast_utf8_string_interp.ml | 3 - compiler/frontend/ast_utf8_string_interp.mli | 1 - compiler/frontend/bs_ast_invariant.ml | 3 - compiler/ml/typecore.ml | 34 +----- 8 files changed, 3 insertions(+), 161 deletions(-) diff --git a/compiler/common/bs_warnings.ml b/compiler/common/bs_warnings.ml index aeea997b82d..61e55032eea 100644 --- a/compiler/common/bs_warnings.ml +++ b/compiler/common/bs_warnings.ml @@ -24,6 +24,3 @@ let warn_literal_overflow loc = Location.prerr_warning loc Bs_integer_literal_overflow - -let error_unescaped_delimiter loc txt = - Location.prerr_warning loc (Bs_uninterpreted_delimiters txt) diff --git a/compiler/common/bs_warnings.mli b/compiler/common/bs_warnings.mli index 3807bbf32c6..d31e0846070 100644 --- a/compiler/common/bs_warnings.mli +++ b/compiler/common/bs_warnings.mli @@ -23,5 +23,3 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) val warn_literal_overflow : Location.t -> unit - -val error_unescaped_delimiter : Location.t -> string -> unit diff --git a/compiler/ext/warnings.ml b/compiler/ext/warnings.ml index 936f34c4e02..3bbb738346c 100644 --- a/compiler/ext/warnings.ml +++ b/compiler/ext/warnings.ml @@ -29,21 +29,13 @@ type loc = { type top_level_unit_help = FunctionCall | Other type t = - | Comment_start (* 1 *) - | Comment_not_end (* 2 *) | Deprecated of string * loc * loc * bool (* 3 *) | Fragile_match of string (* 4 *) | Partial_application (* 5 *) - | Method_override of string list (* 7 *) | Partial_match of string (* 8 *) | Non_closed_record_pattern of string (* 9 *) - | Statement_type (* 10 *) | Unused_match (* 11 *) | Unused_pat (* 12 *) - | Instance_variable_override of string list (* 13 *) - | Illegal_backslash (* 14 *) - | Implicit_public_methods of string list (* 15 *) - | Unerasable_optional_argument (* 16 *) | Unused_argument (* 20 *) | Nonreturning_statement (* 21 *) | Preprocessor of string (* 22 *) @@ -53,7 +45,6 @@ type t = | Unused_var of string (* 26 *) | Unused_var_strict of string (* 27 *) | Wildcard_arg_to_constant_constr (* 28 *) - | Eol_in_string (* 29 *) | Duplicate_definitions of string * string * string * string (*30 *) | Unused_value_declaration of string (* 32 *) | Unused_open of string (* 33 *) @@ -67,9 +58,7 @@ type t = | Open_shadow_identifier of string * string (* 44 *) | Open_shadow_label_constructor of string * string (* 45 *) | Attribute_payload of string * string (* 47 *) - | Eliminated_optional_arguments of string list (* 48 *) | No_cmi_file of string * string option (* 49 *) - | Bad_docstring of bool (* 50 *) | Fragile_literal_pattern (* 52 *) | Misplaced_attribute of string (* 53 *) | Duplicated_attribute of string (* 54 *) @@ -81,10 +70,7 @@ type t = | Bs_polymorphic_comparison (* 102 *) | Bs_ffi_warning of string (* 103 *) | Bs_derive_warning of string (* 104 *) - | Bs_fragile_external of string (* 105 *) - | Bs_unimplemented_primitive of string (* 106 *) | Bs_integer_literal_overflow (* 107 *) - | Bs_uninterpreted_delimiters of string (* 108 *) | Bs_toplevel_expression_unit of (string * top_level_unit_help) option (* 109 *) | Bs_todo of string option (* 110 *) @@ -96,21 +82,13 @@ type t = *) let number = function - | Comment_start -> 1 - | Comment_not_end -> 2 | Deprecated _ -> 3 | Fragile_match _ -> 4 | Partial_application -> 5 - | Method_override _ -> 7 | Partial_match _ -> 8 | Non_closed_record_pattern _ -> 9 - | Statement_type -> 10 | Unused_match -> 11 | Unused_pat -> 12 - | Instance_variable_override _ -> 13 - | Illegal_backslash -> 14 - | Implicit_public_methods _ -> 15 - | Unerasable_optional_argument -> 16 | Unused_argument -> 20 | Nonreturning_statement -> 21 | Preprocessor _ -> 22 @@ -120,7 +98,6 @@ let number = function | Unused_var _ -> 26 | Unused_var_strict _ -> 27 | Wildcard_arg_to_constant_constr -> 28 - | Eol_in_string -> 29 | Duplicate_definitions _ -> 30 | Unused_value_declaration _ -> 32 | Unused_open _ -> 33 @@ -134,9 +111,7 @@ let number = function | Open_shadow_identifier _ -> 44 | Open_shadow_label_constructor _ -> 45 | Attribute_payload _ -> 47 - | Eliminated_optional_arguments _ -> 48 | No_cmi_file _ -> 49 - | Bad_docstring _ -> 50 | Fragile_literal_pattern -> 52 | Misplaced_attribute _ -> 53 | Duplicated_attribute _ -> 54 @@ -148,10 +123,7 @@ let number = function | Bs_polymorphic_comparison -> 102 | Bs_ffi_warning _ -> 103 | Bs_derive_warning _ -> 104 - | Bs_fragile_external _ -> 105 - | Bs_unimplemented_primitive _ -> 106 | Bs_integer_literal_overflow -> 107 - | Bs_uninterpreted_delimiters _ -> 108 | Bs_toplevel_expression_unit _ -> 109 | Bs_todo _ -> 110 @@ -297,8 +269,6 @@ let reset () = let () = reset () let message = function - | Comment_start -> "this is the start of a comment." - | Comment_not_end -> "this is not the end of a comment." | Deprecated (s, _, _, can_be_automigrated) -> (* Reduce \r\n to \n: - Prevents any \r characters being printed on Unix when processing @@ -321,12 +291,6 @@ let message = function It will remain exhaustive when constructors are added to type " ^ s ^ "." | Partial_application -> "this function application is partial,\nmaybe some arguments are missing." - | Method_override [lab] -> "the method " ^ lab ^ " is overridden." - | Method_override (cname :: slist) -> - String.concat " " - ("the following methods are overridden by the class" :: cname :: ":\n " - :: slist) - | Method_override [] -> assert false | Partial_match "" -> "You forgot to handle a possible case here, though we don't have more \ information on the value." @@ -335,42 +299,8 @@ let message = function | Non_closed_record_pattern s -> "the following labels are not bound in this record pattern: " ^ s ^ "\nEither bind these labels explicitly or add ', _' to the pattern." - | Statement_type -> - "This expression returns a value, but you're not doing anything with it. \ - If this is on purpose, wrap it with `ignore`." | Unused_match -> "this match case is unused." | Unused_pat -> "this sub-pattern is unused." - | Instance_variable_override [lab] -> - "the instance variable " ^ lab ^ " is overridden.\n" - ^ "The behaviour changed in ocaml 3.10 (previous behaviour was hiding.)" - | Instance_variable_override (cname :: slist) -> - String.concat " " - ("the following instance variables are overridden by the class" :: cname - :: ":\n " :: slist) - ^ "\nThe behaviour changed in ocaml 3.10 (previous behaviour was hiding.)" - | Instance_variable_override [] -> assert false - | Illegal_backslash -> "illegal backslash escape in string." - | Implicit_public_methods l -> - "the following private methods were made public implicitly:\n " - ^ String.concat " " l ^ "." - | Unerasable_optional_argument -> - String.concat "" - [ - "This optional parameter in final position will, in practice, not be \ - optional.\n"; - " Reorder the parameters so that at least one non-optional one is in \ - final position or, if all parameters are optional, insert a final \ - ().\n\n"; - " Explanation: If the final parameter is optional, it'd be unclear \ - whether a function application that omits it should be considered \ - fully applied, or partially applied. Imagine writing `let title = \ - display(\"hello!\")`, only to realize `title` isn't your desired \ - result, but a curried call that takes a final optional argument, e.g. \ - `~showDate`.\n\n"; - " Formal rule: an optional argument is considered intentionally \ - omitted when the 1st positional (i.e. neither labeled nor optional) \ - argument defined after it is passed in."; - ] | Unused_argument -> "this argument will not be used by the function." | Nonreturning_statement -> "This statement does not continue execution; following code is unreachable." @@ -398,8 +328,6 @@ let message = function v v | Wildcard_arg_to_constant_constr -> "wildcard pattern given as argument to a constant constructor" - | Eol_in_string -> - "unescaped end-of-line in a string constant (non-portable code)" | Duplicate_definitions (kind, cname, tc1, tc2) -> Printf.sprintf "the %s %s is defined in both types %s and %s." kind cname tc1 tc2 @@ -446,18 +374,11 @@ let message = function kind s | Attribute_payload (a, s) -> Printf.sprintf "illegal payload for attribute '%s'.\n%s" a s - | Eliminated_optional_arguments sl -> - Printf.sprintf "implicit elimination of optional argument%s %s" - (if List.length sl = 1 then "" else "s") - (String.concat ", " sl) | No_cmi_file (name, None) -> "no cmi file was found in path for module " ^ name | No_cmi_file (name, Some msg) -> Printf.sprintf "no valid cmi file was found in path for module %s. %s" name msg - | Bad_docstring unattached -> - if unattached then "unattached documentation comment (ignored)" - else "ambiguous documentation comment" | Fragile_literal_pattern -> Printf.sprintf "Code should not depend on the actual values of\n\ @@ -495,15 +416,8 @@ let message = function "Polymorphic comparison introduced (maybe unsafe)" | Bs_ffi_warning s -> "FFI warning: " ^ s | Bs_derive_warning s -> "@deriving warning: " ^ s - | Bs_fragile_external s -> - s - ^ " : using an empty string as a shorthand to infer the external's name \ - from the value's name is dangerous when refactoring, and therefore \ - deprecated" - | Bs_unimplemented_primitive s -> "Unimplemented primitive used: " ^ s | Bs_integer_literal_overflow -> "Integer literal exceeds the range of representable integers of type int" - | Bs_uninterpreted_delimiters s -> "Uninterpreted delimiters " ^ s | Bs_toplevel_expression_unit help -> Printf.sprintf "This%sis at the top level and is expected to return `unit`. But it's \ @@ -581,8 +495,6 @@ let check_fatal () = let descriptions = [ - (1, "Suspicious-looking start-of-comment mark."); - (2, "Suspicious-looking end-of-comment mark."); (3, "Deprecated feature."); ( 4, "Fragile pattern matching: matching that will remain complete even\n\ @@ -591,18 +503,10 @@ let descriptions = ( 5, "Partially applied function: expression whose result has function\n\ \ type and is ignored." ); - (7, "Method overridden."); (8, "Partial match: missing cases in pattern-matching."); (9, "Missing fields in a record pattern."); - ( 10, - "Expression on the left-hand side of a sequence that doesn't have type\n\ - \ \"unit\" (and that is not a function, see warning number 5)." ); (11, "Redundant case in a pattern matching (unused match case)."); (12, "Redundant sub-pattern in a pattern-matching."); - (13, "Instance variable overridden."); - (14, "Illegal backslash escape in a string constant."); - (15, "Private method made public implicitly."); - (16, "Unerasable optional argument."); (17, "Undeclared virtual method."); (18, "Non-principal type."); (19, "Type without principality."); @@ -623,7 +527,6 @@ let descriptions = \ \"let\" nor \"as\", and doesn't start with an underscore (\"_\")\n\ \ character." ); (28, "Wildcard pattern given as argument to a constant constructor."); - (29, "Unescaped end-of-line in a string constant (non-portable code)."); ( 30, "Two labels or constructors of the same name are defined in two\n\ \ mutually recursive types." ); @@ -642,9 +545,7 @@ let descriptions = (45, "Open statement shadows an already defined label or constructor."); (46, "Error in environment variable."); (47, "Illegal attribute payload."); - (48, "Implicit elimination of optional arguments."); (49, "Absent cmi file when looking up module alias."); - (50, "Unexpected documentation comment."); (52, "Fragile constant pattern."); (53, "Attribute cannot appear in this context"); (54, "Attribute used more than once on an expression"); @@ -658,14 +559,9 @@ let descriptions = (102, "Polymorphic comparison introduced (maybe unsafe)"); (103, "Fragile FFI definitions"); (104, "@deriving warning with customized message "); - ( 105, - "External name is inferred from val name is unsafe from refactoring when \ - changing value name" ); - (106, "Unimplemented primitive used:"); ( 107, "Integer literal exceeds the range of representable integers of type int" ); - (108, "Uninterpreted delimiters (for unicode)"); (109, "Toplevel expression has unit type"); (110, "Todo found"); ] diff --git a/compiler/ext/warnings.mli b/compiler/ext/warnings.mli index ba1a03ceec5..5ebdfa4b248 100644 --- a/compiler/ext/warnings.mli +++ b/compiler/ext/warnings.mli @@ -22,21 +22,13 @@ type loc = { type top_level_unit_help = FunctionCall | Other type t = - | Comment_start (* 1 *) - | Comment_not_end (* 2 *) | Deprecated of string * loc * loc * bool (* 3 *) | Fragile_match of string (* 4 *) | Partial_application (* 5 *) - | Method_override of string list (* 7 *) | Partial_match of string (* 8 *) | Non_closed_record_pattern of string (* 9 *) - | Statement_type (* 10 *) | Unused_match (* 11 *) | Unused_pat (* 12 *) - | Instance_variable_override of string list (* 13 *) - | Illegal_backslash (* 14 *) - | Implicit_public_methods of string list (* 15 *) - | Unerasable_optional_argument (* 16 *) | Unused_argument (* 20 *) | Nonreturning_statement (* 21 *) | Preprocessor of string (* 22 *) @@ -46,7 +38,6 @@ type t = | Unused_var of string (* 26 *) | Unused_var_strict of string (* 27 *) | Wildcard_arg_to_constant_constr (* 28 *) - | Eol_in_string (* 29 *) | Duplicate_definitions of string * string * string * string (* 30 *) | Unused_value_declaration of string (* 32 *) | Unused_open of string (* 33 *) @@ -60,9 +51,7 @@ type t = | Open_shadow_identifier of string * string (* 44 *) | Open_shadow_label_constructor of string * string (* 45 *) | Attribute_payload of string * string (* 47 *) - | Eliminated_optional_arguments of string list (* 48 *) | No_cmi_file of string * string option (* 49 *) - | Bad_docstring of bool (* 50 *) | Fragile_literal_pattern (* 52 *) | Misplaced_attribute of string (* 53 *) | Duplicated_attribute of string (* 54 *) @@ -74,10 +63,7 @@ type t = | Bs_polymorphic_comparison (* 102 *) | Bs_ffi_warning of string (* 103 *) | Bs_derive_warning of string (* 104 *) - | Bs_fragile_external of string (* 105 *) - | Bs_unimplemented_primitive of string (* 106 *) | Bs_integer_literal_overflow (* 107 *) - | Bs_uninterpreted_delimiters of string (* 108 *) | Bs_toplevel_expression_unit of (string * top_level_unit_help) option (* 109 *) | Bs_todo of string option (* 110 *) diff --git a/compiler/frontend/ast_utf8_string_interp.ml b/compiler/frontend/ast_utf8_string_interp.ml index 90c5043256e..e3e65018a77 100644 --- a/compiler/frontend/ast_utf8_string_interp.ml +++ b/compiler/frontend/ast_utf8_string_interp.ml @@ -289,7 +289,6 @@ module Delim = struct let escaped_j_delimiter = "*j" (* not user level syntax allowed *) let escaped_back_quote_delimiter = "bq" let some_escaped_back_quote_delimiter = Some "bq" - let unescaped_js_delimiter = "js" let some_escaped_j_delimiter = Some escaped_j_delimiter end @@ -339,6 +338,4 @@ let is_unicode_string opt = Ext_string.equal opt Delim.escaped_j_delimiter || Ext_string.equal opt Delim.escaped_back_quote_delimiter -let is_unescaped s = Ext_string.equal s Delim.unescaped_js_delimiter - let parse_processed_delim = Delim.parse_processed diff --git a/compiler/frontend/ast_utf8_string_interp.mli b/compiler/frontend/ast_utf8_string_interp.mli index 5e8ec9c8a91..bc0b13c93ee 100644 --- a/compiler/frontend/ast_utf8_string_interp.mli +++ b/compiler/frontend/ast_utf8_string_interp.mli @@ -59,5 +59,4 @@ val transform_exp : Parsetree.expression -> string -> string -> Parsetree.expression val transform_pat : Parsetree.pattern -> string -> string -> Parsetree.pattern val is_unicode_string : string -> bool -val is_unescaped : string -> bool val parse_processed_delim : string option -> External_arg_spec.delim option diff --git a/compiler/frontend/bs_ast_invariant.ml b/compiler/frontend/bs_ast_invariant.ml index 60e1ad285d0..023043f9f44 100644 --- a/compiler/frontend/bs_ast_invariant.ml +++ b/compiler/frontend/bs_ast_invariant.ml @@ -52,9 +52,6 @@ let super = Ast_iterator.default_iterator let check_constant loc (const : Parsetree.constant) = match const with - | Pconst_string (_, Some s) -> - if Ast_utf8_string_interp.is_unescaped s then - Bs_warnings.error_unescaped_delimiter loc s | Pconst_integer (s, None) -> ( (* range check using int32 It is better to give a warning instead of error to avoid make people unhappy. diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 8e6da258c69..182a43e8632 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -1986,19 +1986,6 @@ let rec type_approx env sexp = ty2 | _ -> newvar () -(* List labels in a function type, and whether return type is a variable *) -let rec list_labels_aux env visited ls ty_fun = - let ty = expand_head env ty_fun in - if List.memq ty visited then (List.rev ls, false) - else - match ty.desc with - | Tarrow (arg, ty_res, _, arity) when arity = None || visited = [] -> - list_labels_aux env (ty :: visited) (arg.lbl :: ls) ty_res - | _ -> (List.rev ls, is_Tvar ty) - -let list_labels env ty = - wrap_trace_gadt_instances env (list_labels_aux env [] []) ty - (* Check that all univars are safe in a type *) let check_univars env expans kind exp ty_expected vars = if expans && not (is_nonexpansive exp) then @@ -2028,13 +2015,12 @@ let check_univars env expans kind exp ty_expected vars = Less_general (kind, [(ty, ty); (ty_expected, ty_expected)]) )) (* Check that a type is not a function *) -let check_application_result env statement exp = - let loc = exp.exp_loc in +let check_application_result env exp = match (expand_head env exp.exp_type).desc with | Tarrow _ -> Location.prerr_warning exp.exp_loc Warnings.Partial_application | Tvar _ -> () | Tconstr (p, _, _) when Path.same p Predef.path_unit -> () - | _ -> if statement then Location.prerr_warning loc Warnings.Statement_type + | _ -> () (* Check that a type is generalizable at some level *) let generalizable level ty = @@ -2271,10 +2257,6 @@ let rec lower_args env seen ty_fun = lower_args env (ty :: seen) ty_fun | _ -> () -let not_function env ty = - let ls, tvar = list_labels env ty in - ls = [] && not tvar - let extract_function_name funct = match funct.exp_desc with | Texp_ident (path, _, _) -> Some (Longident.parse (Path.name path)) @@ -3460,12 +3442,6 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l caselist = - let state = Warnings.backup () in - (* Disable Unerasable_optional_argument for uncurried functions *) - let unerasable_optional_argument = - Warnings.number Unerasable_optional_argument - in - Warnings.parse_options false ("-" ^ string_of_int unerasable_optional_argument); let ty_expected = match arity with | None -> ty_expected_ @@ -3506,15 +3482,11 @@ and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l ty_arg ty_res true loc caselist) in let case = List.hd cases in - if is_optional l && not_function env ty_res then - Location.prerr_warning case.c_lhs.pat_loc - Warnings.Unerasable_optional_argument; let param = name_pattern "param" cases in let exp_type = instance env (newgenty (Tarrow ({lbl = l; typ = ty_arg}, ty_res, Cok, arity))) in - Warnings.restore state; re { exp_desc = @@ -3959,7 +3931,7 @@ and type_application ~context total_app env funct (sargs : sargs) : Location.prerr_warning exp.exp_loc Warnings.Partial_application | Tvar _ -> Delayed_checks.add_delayed_check (fun () -> - check_application_result env false exp) + check_application_result env exp) | _ -> ()); ([(Nolabel, Some exp)], ty_res, false) | _ -> From bcf2d8693922d583f220020ffe7cc4fc448cc152 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:31:11 +0000 Subject: [PATCH 12/26] Format dead error cleanup Run the repository formatter after removing the unreachable diagnostics. This keeps the edited compiler modules passing make checkformat without changing behavior. --- compiler/ml/ctype.ml | 4 ++-- compiler/ml/typecore.ml | 44 ++++++++++++++++++++--------------------- compiler/ml/typedecl.ml | 9 ++++----- compiler/ml/typemod.ml | 13 ++++++------ compiler/ml/typetexp.ml | 3 ++- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/compiler/ml/ctype.ml b/compiler/ml/ctype.ml index 1291da7880c..6eba569edad 100644 --- a/compiler/ml/ctype.ml +++ b/compiler/ml/ctype.ml @@ -2709,8 +2709,8 @@ and unify_row_field env fixed1 fixed2 more l f1 f2 = let unify env ty1 ty2 = let snap = Btype.snapshot () in - try unify env ty1 ty2 with - | Unify trace -> + try unify env ty1 ty2 + with Unify trace -> undo_compress snap; raise (Unify (expand_trace !env trace)) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 182a43e8632..a3774ffed24 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -330,13 +330,14 @@ let check_optional_attr env ld optional loc = (* unification inside type_pat*) let unify_pat_types loc env ty ty' = - try unify env ty ty' with - | Unify trace -> raise (Error (loc, env, Pattern_type_clash trace)) + try unify env ty ty' + with Unify trace -> raise (Error (loc, env, Pattern_type_clash trace)) (* unification inside type_exp and type_expect *) let unify_exp_types ~context loc env ty expected_ty = - try unify env ty expected_ty with - | Unify trace -> raise (Error (loc, env, Expr_type_clash {trace; context})) + try unify env ty expected_ty + with Unify trace -> + raise (Error (loc, env, Expr_type_clash {trace; context})) (* level at which to create the local type declarations *) let newtype_level = ref None @@ -351,8 +352,8 @@ let unify_pat_types_gadt loc env ty ty' = | None -> assert false | Some x -> x in - try unify_gadt ~newtype_level env ty ty' with - | Unify trace -> raise (Error (loc, !env, Pattern_type_clash trace)) + try unify_gadt ~newtype_level env ty ty' + with Unify trace -> raise (Error (loc, !env, Pattern_type_clash trace)) (* Creating new conjunctive types is not allowed when typing patterns *) @@ -3461,9 +3462,9 @@ and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l if separate then begin_def (); let ty_arg, ty_res = try filter_arrow ~env ~arity (instance env ty_expected) l - with Unify _ -> ( + with Unify _ -> raise - (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun)))) + (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun))) in let ty_arg = if is_optional l then ( @@ -4458,20 +4459,19 @@ let type_expression ~context env sexp = Typetexp.reset_type_variables (); begin_def (); let exp = type_exp ~context env sexp in - (if Warnings.is_active (Bs_toplevel_expression_unit None) then - try unify env exp.exp_type (instance_def Predef.type_unit) with - | Unify _ -> - let buffer = Buffer.create 10 in - let formatter = Format.formatter_of_buffer buffer in - Printtyp.type_expr formatter exp.exp_type; - Format.pp_print_flush formatter (); - let return_type = Buffer.contents buffer in - Location.prerr_warning sexp.pexp_loc - (Bs_toplevel_expression_unit - (match sexp.pexp_desc with - | Pexp_apply _ -> Some (return_type, FunctionCall) - | _ -> Some (return_type, Other))) - ); + if Warnings.is_active (Bs_toplevel_expression_unit None) then ( + try unify env exp.exp_type (instance_def Predef.type_unit) + with Unify _ -> + let buffer = Buffer.create 10 in + let formatter = Format.formatter_of_buffer buffer in + Printtyp.type_expr formatter exp.exp_type; + Format.pp_print_flush formatter (); + let return_type = Buffer.contents buffer in + Location.prerr_warning sexp.pexp_loc + (Bs_toplevel_expression_unit + (match sexp.pexp_desc with + | Pexp_apply _ -> Some (return_type, FunctionCall) + | _ -> Some (return_type, Other)))); end_def (); if not (is_nonexpansive exp) then generalize_expansive env exp.exp_type; generalize exp.exp_type; diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 39eef5fed4f..4e0f6d31cab 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -183,8 +183,7 @@ let set_fixed_row env loc p decl = if Btype.static_row row then Btype.newgenty Tnil else row.row_more | Tobject (ty, _) -> snd (Ctype.flatten_fields ty) | _ -> - Location.raise_errorf ~loc - "This fixed type is not an object or variant" + Location.raise_errorf ~loc "This fixed type is not an object or variant" in if not (Btype.is_Tvar rv) then Location.raise_errorf ~loc "This fixed type has no row variable"; @@ -979,9 +978,9 @@ let check_recursion env loc path decl to_check = | Tconstr (path', args', _) -> (if Path.same path path' then ( if not (Ctype.equal env false args args') then - Location.raise_errorf ~loc - "In the definition of %s, recursive type parameters differ." - (Path.name cpath)) + Location.raise_errorf ~loc + "In the definition of %s, recursive type parameters differ." + (Path.name cpath)) else if (* Attempt to expand a type abbreviation if: 1- [to_check path'] holds diff --git a/compiler/ml/typemod.ml b/compiler/ml/typemod.ml index 7fec8d74db0..48cfba0b627 100644 --- a/compiler/ml/typemod.ml +++ b/compiler/ml/typemod.ml @@ -247,10 +247,11 @@ let check_usage_of_path_of_substituted_item paths env signature ~loc ~lid = try retype_applicative_functor_type ~loc env funct arg with Includemod.Error explanation -> Location.raise_errorf ~loc - "@[@[This `with' constraint on %a makes the \ - applicative functor type %s ill-typed in the constrained \ + "@[@[This `with' constraint on %a makes the applicative \ + functor type %s ill-typed in the constrained \ signature:@]@ %a@]" - Printtyp.longident lid.txt (Path.name referenced_path) + Printtyp.longident lid.txt + (Path.name referenced_path) Includemod.report_error explanation)); } in @@ -437,7 +438,8 @@ let merge_constraint initial_env loc sg constr = Location.raise_errorf ~loc "@[Destructive substitutions are not supported for \ constrained types (other than when replacing a type \ - constructor with a type constructor with the same arguments).@]"; + constructor with a type constructor with the same \ + arguments).@]"; fun s path -> Subst.add_type_function path ~params ~body s in let sub = List.fold_left how_to_extend_subst Subst.identity !real_ids in @@ -1331,8 +1333,7 @@ and type_module_aux ~alias sttn funct_body anchor env smod = "@[This functor has type@ %a@ The parameter cannot be \ eliminated in the result type.@ Bind the argument to a \ module identifier.@]" - Printtyp.modtype mty_functor - ) + Printtyp.modtype mty_functor) in rm { diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index 12e1a36edf9..c958eb4b1db 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -618,7 +618,8 @@ and transl_fields env policy o fields = OTinherit cty | {desc = Tvar _}, Some p -> Location.raise_errorf ~loc:sty.ptyp_loc - "The type constructor %a is not yet completely defined" Printtyp.path p + "The type constructor %a is not yet completely defined" Printtyp.path + p | _ -> raise (Error (sty.ptyp_loc, env, Not_an_object t))) in let object_fields = List.map add_field fields in From 5590d821291931caf1192923edf183757ae805fd Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:31:26 +0000 Subject: [PATCH 13/26] Update error variant removal audit Record the diagnostics removed on jono/remove-dead-errors and remove their table rows. Reclassify the audited items that are not completely dead, including stale-CMI module-type paths, the live Syntaxerr printer gap, and UTF-8 helper errors retained for defensive/test entry points. --- tests/ERROR_VARIANTS.md | 255 ++++++++++++---------------------------- 1 file changed, 72 insertions(+), 183 deletions(-) diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 6f45ebbbf4b..d9f8a00ac24 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -11,8 +11,7 @@ The catalog has two practical uses: AST shape. 2. **Dead-code removal** — rows tagged `⚠` are variants whose trigger site is unreachable in the current parser / compiler, with a named - blocker. They can be deleted in a follow-up PR. The "Confirmed dead" - summary at the bottom groups them by reason. + blocker. They can be deleted in a follow-up PR. ## Status legend @@ -23,7 +22,8 @@ The catalog has two practical uses: | ☐ | Reachable but no fixture yet; would be valuable to add. | | ? | Trigger site is live but reachability from regular ReScript source isn't confirmed. Distinct from ⚠: a `?` means "I couldn't find a fixture that reaches it" rather than "the path is provably blocked". | -The "Confirmed dead" summary section at the bottom only includes ⚠. +The removal audit section below records variants that have already been +deleted or retained after re-validation. ## Scope @@ -55,6 +55,41 @@ If a variant turns out to be unreachable, document the named blocker here (so it gets ⚠ instead of `?`) and file a follow-up to delete the dead code. +## Removed in `jono/remove-dead-errors` + +The following named error and warning variants were re-validated as +unreachable and removed. Guard sites that could still be reached by +malformed PPX-produced ASTs now use direct `Location.raise_errorf` +fallbacks instead of catalogued variants. + +- `typecore`: `Label_mismatch`, `Abstract_wrong_label`, + `Incoherent_label_order`, `Recursive_local_constraint`, + `Invalid_interval`, `Invalid_for_of_pattern` +- `typedecl`: `Type_clash`, `Parameters_differ`, + `Null_arity_external`, `Rebind_wrong_type`, `Bad_fixed_type`, + `Varying_anonymous`, `Val_in_structure` +- `typemod`: `Cannot_eliminate_dependency`, + `With_makes_applicative_functor_ill_typed`, + `With_cannot_remove_constrained_type`, `Scoping_pack` +- `typetexp`: `Unbound_type_constructor_2`, `Variant_tags`, + `Ill_typed_functor_application`, `Apply_structure_as_functor` +- `bs_syntaxerr`: `Conflict_bs_bs_this_bs_meth`, + `Unhandled_poly_type`, `Misplaced_label_syntax` +- `env`: `Illegal_value_name` +- `warnings`: `Comment_start`, `Comment_not_end`, `Method_override`, + `Statement_type`, `Instance_variable_override`, `Illegal_backslash`, + `Implicit_public_methods`, `Unerasable_optional_argument`, + `Eol_in_string`, `Eliminated_optional_arguments`, `Bad_docstring`, + `Bs_fragile_external`, `Bs_unimplemented_primitive`, + `Bs_uninterpreted_delimiters` + +Re-validation also found a few previously-flagged items that are not +completely dead: `includemod.Unbound_modtype_path` can still represent a +stale compiled-interface failure, `Syntaxerr.Variable_in_scope` is live +but lacks a registered printer, and the UTF-8 helper error families are +still raised by test/defensive helper entry points. Those are retained +below with updated notes. + --- ## `compiler/ml/typecore.ml` @@ -66,7 +101,6 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). |---|---|---|---| | `Polymorphic_label` | ✓ | `polymorphic_label.res` | Pattern that instantiates a polymorphic record field: `({f: (f: int => int)}: t) =>` constrains the universal `'a` of `f: 'a. 'a => 'a` to `int => int`. | | `Constructor_arity_mismatch` | ✓ | `constructor_arity_mismatch.res`, `constructor_arity_mismatch_pattern.res`, `arity_mismatch*.res` | Triggers in both expression (4028) and pattern (1426) paths. | -| `Label_mismatch` | ⚠ | — | typecore.ml:1543/3589. Defensive `try unify ty_res ty_expected with Unify -> Label_mismatch`. The only way to reach the unify is after label disambiguation (`type_label_a_list` / `type_label_pat` / `Wrong_name`-style logic), which always locks `ty_res` to a record type already unifiable with the expected. The unify therefore can't fail in practice — every reproduction hits `Wrong_name`, `Pattern_type_clash`, or `Expr_type_clash` instead. The constructor is a defensive leftover from the OCaml inheritance. | | `Pattern_type_clash` | ✓ | many `*_pattern_type_clash.res` etc. | Most-fired pattern error. Sub-case fixtures: `pattern_matching_on_option_but_value_not_option.res` and `pattern_matching_on_value_but_is_option.res` (option-vs-non-option trace), `pattern_type_clash_polyvariant.res` (polyvariant tag against concrete type), `pattern_type_clash_tuple_arity.res` (tuple arity mismatch). | | `Or_pattern_type_clash` | ✓ | `or_pattern_type_clash.res` | | | `Multiply_bound_variable` | ✓ | `multiply_bound_variable.res` | | @@ -84,20 +118,15 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). | `Private_label` | ✓ | `private_label.res` | | | `Not_subtype` | ✓ | `subtype_*.res`, `dict_show_no_coercion.res`, etc. | | | `Too_many_arguments` | ✓ | `too_many_arguments.res`, `moreArguments*.res` | | -| `Abstract_wrong_label` | ⚠ | — | typecore.ml:3502. Fires in `type_function` when `filter_arrow` with the literal's label fails *and* the expected type is `Tarrow`. In modern ReScript the function literal's type is fully inferred from its own args first, then unified — that path emits `Expr_type_clash`, not `Abstract_wrong_label`. Several attempted reproductions all surfaced as `Expr_type_clash`. Treating as effectively dead. | | `Scoping_let_module` | ✓ | `scoping_let_module.res` | | | `Not_a_variant_type` | ✓ | `variant_spread_pattern_not_a_variant.res` | Pattern-level variant spread of a non-variant type. | -| `Incoherent_label_order` | ⚠ | — | typecore.ml:3894. Reached only after `arity_ok` is true *and* the label is present in `ty_fun` but not at the current arrow position. ReScript's labeled-argument reordering happens earlier in `type_args` / `type_unknown_args`, so by the time we hit this branch the label is already at the right position. Every attempted reproduction landed on `Apply_wrong_label` or `Expr_type_clash`. | | `Less_general` | ✓ | `less_general_universal.res` | | | `Modules_not_allowed` | ✓ | `super_errors_multi/Modules_not_allowed_toplevel` | Toplevel `let module(M) = …` pattern with `allow_modules=false`. | | `Cannot_infer_signature` | ✓ | `cannot_infer_signature.res` | | | `Not_a_packed_module` | ✓ | `not_a_packed_module.res` | | -| `Recursive_local_constraint` | ⚠ | — | typecore.ml:369. Routed via `Unification_recursive_abbrev` in `ctype.ml`, which is raised only when `ctype.ml`'s `Recursive_abbrev` exception fires. **`Recursive_abbrev` is defined (ctype.ml:110, ctype.mli:61) but never raised anywhere in `compiler/`.** Confirmed dead. | | `Unexpected_existential` | ✓ | `super_errors_multi/Unexpected_existential_in_let` | Destructuring GADT constructor with existential in toplevel `let`. | | `Unqualified_gadt_pattern` | ✓ | `super_errors_multi/Cross_gadt_pattern` | Only reachable via cross-module GADT disambiguation; in single-file matching the constructor would resolve before this check. | -| `Invalid_interval` | ⚠ | — | typecore.ml:1349. Triggered by `Ppat_interval` pattern. **Verified: `Ppat_interval` has no construction site in `compiler/syntax/src/res_core.ml`** — only printer and ast_debugger handle it. | | `Invalid_for_loop_index` | ✓ | `invalid_for_loop_index.res` | | -| `Invalid_for_of_pattern` | ⚠ | — | typecore.ml:3120/3152. Verified: parser `normalize_for_of_pattern` (`res_core.ml:3841`) replaces non-var / non-`_` patterns with `Ppat_any` before the typer sees them. | | `No_value_clauses` | ✓ | `no_value_clauses.res` | | | `Exception_pattern_below_toplevel` | ✓ | `exception_pattern_below_toplevel.res` | | | `Inlined_record_escape` | ✓ | `inline_record_escape.res` | | @@ -133,22 +162,15 @@ Type-declaration errors. Source: [typedecl.ml:27](../compiler/ml/typedecl.ml). | `Definition_mismatch` | ✓ | `definition_mismatch.res` | | | `Constraint_failed` | ✓ | `constraint_failed.res` | | | `Inconsistent_constraint` | ✓ | `inconsistent_constraint.res` | | -| `Type_clash` | ⚠ | — | typedecl.ml:125. Fires when `Ctype.unify env (newconstr path params) manifest` fails inside `update_type` for a `type rec` block. For ReScript types this unify either trivially succeeds (aliases unify with their manifest because the cycle/arity machinery has already accepted the shape) or the declaration is rejected earlier by `Cycle_in_def` / `Recursive_abbrev`. I couldn't construct a recursive shape that reaches the failing unify without being caught first. | -| `Parameters_differ` | ⚠ | — | typedecl.ml:988. Fires for non-uniform recursive type *abbreviations* (`type rec t<'a> = … t …`). ReScript treats variant types as having a manifest of None, so `check_regular` is a no-op for them. For abbreviations, `Cycle_in_def` fires first because the recursive reference is direct. I couldn't construct an abbreviation shape that hits Parameters_differ without being cyclic. | -| `Null_arity_external` | ⚠ | — | typedecl.ml:1900. The guard requires `prim_arity = 0` and `prim_native_name` not having the magic 20-byte encoding (`\132\149...`) and `prim_name` not starting with `%` or `#`. The encoding gets applied to every concrete external by `Primitive.parse_declaration`, and empty `prim_name` is rejected earlier by `external_ffi_types.ml` with "Not a valid global name". No path through the parser reaches it. | | `Unbound_type_var` | ✓ | `unbound_type_var.res` | | | `Cannot_extend_private_type` | ✓ | `cannot_extend_private_type.res` | | | `Not_extensible_type` | ✓ | `not_extensible_type.res` | | | `Extension_mismatch` | ✓ | `extension_arity_mismatch.res` | `type t<'a> = ..` extended with `type t += A(int)` — arity differs from the extensible type. | -| `Rebind_wrong_type` | ⚠ | — | typedecl.ml:1653. The unify is `cstr_res` (source constructor's result, freshly instantiated) against `res` (extension's target type with fresh param vars). For non-GADT sources both sides are `t` and trivially unify; for GADT-style sources (`type t<'a> += A: t`) `cstr_res = t` against `res = t` still unifies (`v1 := int`). The parser doesn't allow rebinding with explicit args (`exception B(string) = A` is rejected at `res_core.ml:6660`), so the result-type relationship is always compatible by construction. | | `Rebind_mismatch` | ✓ | `extension_rebind_mismatch.res` | Rebinding constructor into a different extensible type. | | `Rebind_private` | ✓ | `extension_rebind_private.res` | Rebinding a private extension constructor as public. | | `Bad_variance` | ✓ | `bad_variance.res`, `bad_variance_contra.res` | | | `Unavailable_type_constructor` | ☐ (needs build harness) | — | typedecl.ml:778. Requires a type path findable at parse time but missing during constraint enforcement; only cross-unit scenarios where a `.cmi` was found but later removed. | -| `Bad_fixed_type` | ⚠ | — | typedecl.ml:190/193. `set_fixed_row` runs only when `is_fixed_type` returns true, which requires a `private` abstract type with a syntactically open object / polyvariant manifest (typedecl.ml:160-174). For a manifest written that way, `expand_head` returns exactly the same `Tobject` / `Tvariant`, so the check at line 190 passes and the row variable check at line 193 also passes (rows from those syntactic forms have a Tvar `row_more`). No alias chain in ReScript syntax can collapse the open row while still passing `has_row_var` on the syntactic side. | | `Unbound_type_var_ext` | ✓ | `unbound_type_var_extension.res` | | -| `Varying_anonymous` | ⚠ | — | typedecl.ml:1263. Fires in variance computation when an anonymous (`_`) type parameter is constrained against other params under specific variance requirements. ReScript's parser doesn't produce `_` in type parameter position for `type` declarations (`type t<_>` is rejected) — only explicit `'x`-style params, which are never "anonymous" in the sense `Varying_anonymous` checks. | -| `Val_in_structure` | ⚠ | — | typedecl.ml:1887 requires `pval_prim = []` outside a signature. The parser's `external` recovery sets `prim = []` (`res_core.ml:6617`) but only after emitting a `Syntax error`, so the typechecker never reaches the value declaration. From plain source there's no path that produces a non-signature `Val` with empty `pval_prim` — only PPX-rewritten AST could, and the AST shape would have to bypass the parser. | | `Invalid_attribute` | ✓ | `invalid_attribute_not_undefined.res` | | | `Bad_immediate_attribute` | ✓ | `bad_immediate_attribute.res` | | | `Bad_unboxed_attribute` | ✓ | `bad_unboxed_attribute_abstract.res`, `bad_unboxed_attribute_mutable.res`, `bad_unboxed_attribute_many_fields.res`, `bad_unboxed_attribute_extensible.res` | All 4 sub-cases covered. | @@ -167,14 +189,11 @@ Module-level errors. Source: [typemod.ml:24](../compiler/ml/typemod.ml). |---|---|---|---| | `Cannot_apply` | ✓ | `cannot_apply_non_functor.res` | | | `Not_included` | ✓ | All `super_errors_multi/Iface_*` fixtures wrap to this via `compunit`. | | -| `Cannot_eliminate_dependency` | ⚠ | — | typemod.ml:1335. Reached only when `Mtype.nondep_supertype` raises `Not_found` for an anonymous functor application. ReScript's `nondep_supertype` falls back to existential abstraction for any module-typed binding it can't eliminate cleanly, so the `Not_found` branch never fires. Multiple anonymous functor applications (including ones where the result genuinely references the argument's abstract type) all type-check. | | `Signature_expected` | ✓ | `typemod_signature_expected.res` | `with type M.t = …` where `M` is functor-typed inside the outer signature. | | `Structure_expected` | ✓ | `super_errors_multi/Smoke_unbound_module_reference` (indirect); also `open_functor.res` | | | `With_no_component` | ✓ | `with_no_component.res` | | | `With_mismatch` | ✓ | `with_mismatch.res` | | -| `With_makes_applicative_functor_ill_typed` | ⚠ | — | typemod.ml:258. Reached only through the applicative-functor path of `Btype.it_path` (`Papply`); ReScript's parser doesn't emit `Papply` (no parsed construction site in `res_core.ml`), so the iterator never visits this branch. | | `With_changes_module_alias` | ☐ (needs build harness) | — | typemod.ml:240. Fires during `with module := M2` substitution when an aliased sub-module inside the constrained signature is affected. ReScript parses `with module N := M2` (destructive substitution), but constructing a sub-module alias chain that gets invalidated requires multiple `.resi` files and a specific shape I couldn't reproduce single-file. | -| `With_cannot_remove_constrained_type` | ⚠ | — | typemod.ml:443. Fires for `Twith_typesubst` (the `:=` form) when `params_are_constrained` returns true — i.e. the substitution's params are non-`Tvar`. The parser only accepts `'x`-style identifiers in `with type X<…>` param positions (`res_core.ml` rejects `with type x := …` with "Type params start with a singlequote"), so the params are always fresh `Tvar`s and the check never triggers. | | `Repeated_name` | ✓ | `repeated_def_*.res` (multiple) | | | `Non_generalizable` | ✓ | `non_generalizable.res` | | | `Non_generalizable_module` | ✓ | `non_generalizable_module.res` | Nested module containing `let r = ref(None)` — the outer module's `md_type` carries the free `'_weak1` from the inner ref, so `closed_modtype` returns false and the `Sig_module` branch fires. | @@ -182,7 +201,6 @@ Module-level errors. Source: [typemod.ml:24](../compiler/ml/typemod.ml). | `Not_allowed_in_functor_body` | ✓ | `super_errors_multi/not_allowed_in_functor_body` (TODO: confirm path) | | | `Not_a_packed_module` | ✓ | `not_a_packed_module.res` | | | `Incomplete_packed_module` | ✓ | `incomplete_packed_module.res` | | -| `Scoping_pack` | ⚠ | — | typemod.ml:1717. Requires first-class module pack where a constraint type has a level mismatch; very contrived. | | `Recursive_module_require_explicit_type` | ✓ | `recursive_module_require_explicit_type.res` | | | `Apply_generative` | ✓ | `apply_generative.res` | | | `Cannot_scrape_alias` | ☐ (needs build harness) | — | typemod.ml:77, 83, 1347. Requires `Env.scrape_alias` to return `Mty_alias` for an alias whose target `.cmi` couldn't be loaded. The `super_errors_multi` runner pre-compiles every file in the fixture, so the alias target is always present. | @@ -197,7 +215,6 @@ Type-expression errors. Source: [typetexp.ml:28](../compiler/ml/typetexp.ml). |---|---|---|---| | `Unbound_type_variable` | ✓ | (covered indirectly via many fixtures) | | | `Unbound_type_constructor` | ✓ | `typetexp_unbound_type_constructor.res` | | -| `Unbound_type_constructor_2` | ⚠ | — | typetexp.ml:475/619. Reached in two object/polyvariant-inherit code paths when the inherited type is `Tconstr p` and after `expand_head` is still `Tvar` (the body of `p`'s declaration is a bare type variable). ReScript's parser doesn't accept `type t = 'a` at the top level (only via `with type t<'a> = 'a` which doesn't apply here), so the lookup never returns a Tvar-bodied Tconstr. Every reproduction lands on `Not_an_object` or `Not_a_variant`. | | `Type_arity_mismatch` | ✓ | `type_arity_mismatch.res` | | | `Type_mismatch` | ✓ | `typetexp_type_mismatch.res` | Type-constructor application that violates a `constraint 'a = …` on the declaration. | | `Alias_type_mismatch` | ✓ | `typetexp_alias_type_mismatch.res` | | @@ -205,7 +222,6 @@ Type-expression errors. Source: [typetexp.ml:28](../compiler/ml/typetexp.ml). | `Present_has_no_type` | ✓ | `polyvariant_present_has_no_type.res` | `[< #B > #A]` — `#A` is listed as a "present" tag but isn't defined in the polyvariant body. | | `Constructor_mismatch` | ✓ | `polyvariant_constructor_mismatch.res` | | | `Not_a_variant` | ✓ | `typetexp_not_a_variant.res` | Polyvariant `[#X \| a]` where `a` is not a polyvariant. | -| `Variant_tags` | ⚠ | — | typetexp.ml:39. Raised at typecore.ml:342, 349, 367 via `Tags` exception from `ctype.ml`. **Verified: `exception Tags` is defined (ctype.ml:60) but never raised in `compiler/`.** Confirmed dead. | | `Invalid_variable_name` | ✓ | `invalid_type_variable_name.res` | | | `Cannot_quantify` | ✓ | `cannot_quantify.res` | `type t = {f: 'a. (int as 'a) => int}` — `'a` is universally quantified but the alias `int as 'a` rebinds it to `int`, so the proxy is no longer a fresh `Tvar` when the quantification check runs. | | `Multiple_constraints_on_type` | ✓ | `multiple_constraints_on_type.res` | | @@ -215,10 +231,8 @@ Type-expression errors. Source: [typetexp.ml:28](../compiler/ml/typetexp.ml). | `Unbound_label` | ✓ | `typetexp_unbound_label.res` | | | `Unbound_module` | ✓ | `suggest_module_for_missing_identifier.res`, `super_errors_multi/Smoke_unbound_module_reference` | | | `Unbound_modtype` | ✓ | `typetexp_unbound_modtype.res` | | -| `Ill_typed_functor_application` | ⚠ | — | typetexp.ml:102. In the `Longident.Lapply` branch. **Verified: parser has no construction site for `Longident.Lapply`** (no result in `res_core.ml`). Confirmed dead. | | `Illegal_reference_to_recursive_module` | ✓ | `illegal_recursive_module_reference.res` | `module rec A: B.S = …` references another recmodule's module-type before signatures are sealed. During `approx_modtype` of A, `Env.lookup_module B` returns the `#recmod#` placeholder and raises `Env.Recmodule`. | | `Access_functor_as_structure` | ✓ | `access_functor_as_structure.res` | | -| `Apply_structure_as_functor` | ⚠ | — | typetexp.ml:93. In the `Longident.Lapply` branch. Same dead reason as `Ill_typed_functor_application`. | | `Cannot_scrape_alias` | ☐ (needs build harness) | — | typetexp.ml:86 (Ldot path, live), 95/101 (Lapply path, dead since `Lapply` isn't parsed). The live Ldot trigger needs `Env.scrape_alias` to return `Mty_alias` — an alias whose target `.cmi` couldn't be loaded. The `super_errors_multi` harness pre-compiles every alias target. | | `Opened_object` | ✓ | `object_inherit_opened.res` | | | `Not_an_object` | ✓ | `object_inherit_not_an_object.res` | | @@ -239,7 +253,7 @@ Wrapper symptoms attached to inclusion failures. Source: [includemod.ml:23](../c | `Modtype_infos` | ✓ | `super_errors_multi/Iface_modtype_infos` | | | `Modtype_permutation` | ✓ | `super_errors_multi/include_modtype_permutation` | | | `Interface_mismatch` | ✓ | wrapper added to all `Iface_*` failures (line 476). | | -| `Unbound_modtype_path` | ⚠ | — | includemod.ml:94. Fires inside `modtype_path` comparison when `Env.find_modtype` raises `Not_found`. The only callers run after both signatures have been fully typed, so the module-type path is always findable from the local env — `Not_found` would imply a stale `.cmi`, which the multi-file harness can't produce since it always pre-compiles. Treating as dead from the source-only harnesses' point of view. | +| `Unbound_modtype_path` | ☐ (needs stale-cmi harness) | — | includemod.ml:94. Re-validated during removal: not completely dead. It represents `Env.find_modtype` failing during module-type path comparison, which can happen only with a stale or inconsistent compiled interface. The source-only harness cannot produce that state because it pre-compiles every fixture, so this needs a build/binary-state harness rather than deletion. | | `Unbound_module_path` | ☐ (needs build harness) | — | includemod.ml:226/233. Alias comparison where `Env.normalize_path` raises `Not_found`. Requires a module alias whose target `.cmi` is absent at inclusion time — multi-unit only. | | `Invalid_module_alias` | ☐ (needs build harness) | — | includemod.ml:211. Requires both sides `Mty_alias` with one pointing to a functor argument. Reachable only when the alias chain crosses a functor application that the `super_errors_multi` harness doesn't construct. | @@ -279,13 +293,11 @@ FFI / attribute / experimental-feature errors. Source: [bs_syntaxerr.ml:27](../c | Variant | Status | Fixture | Notes | |---|---|---|---| | `Unsupported_predicates` | ✓ | `bs_unsupported_predicates.res` | `@get({weird: true})` on object type field. | -| `Conflict_bs_bs_this_bs_meth` | ⚠ | — | bs_syntaxerr.ml:29 declares the variant but `Bs_syntaxerr.err _ Conflict_bs_bs_this_bs_meth` is **never raised** anywhere in `compiler/`. | | `Duplicated_bs_deriving` | ✓ | `duplicated_bs_deriving.res` | | | `Conflict_attributes` | ✓ | `bs_conflict_attributes.res` | | | `Expect_int_literal` | ✓ | `bs_expect_int_literal.res` | | | `Expect_string_literal` | ✓ | `bs_expect_string_literal.res` | | | `Expect_int_or_string_or_json_literal` | ✓ | `bs_expect_int_or_string_or_json_literal.res` | `@as(true)` on a wildcard external argument. | -| `Unhandled_poly_type` | ⚠ | — | ast_core_type.ml:141. Reached only when an external's arrow chain contains `Ptyp_poly` inline. The parser's `parse_poly_type_expr` only emits `Ptyp_poly` for record field types and explicit `let f: type t. …` annotations; inside arrow chains, the `'a.` is misread as the deprecated `(. …)` uncurried syntax (`res_core.ml` lexer). Inline polytypes in an external's arrow can only come from PPX-rewritten AST. | | `Invalid_underscore_type_in_external` | ✓ | `bs_invalid_underscore_type_in_external.res` | `@obj external make: (~x: _) => _ = ""` — `_` at an optional-label position without `@as`. | | `Invalid_bs_string_type` | ✓ | `bs_invalid_bs_string_type.res` | | | `Invalid_bs_int_type` | ✓ | `bs_invalid_bs_int_type.res` | | @@ -294,7 +306,6 @@ FFI / attribute / experimental-feature errors. Source: [bs_syntaxerr.ml:27](../c | `Illegal_attribute` | ✓ | `bs_illegal_attribute_scope.res` | | | `Not_supported_directive_in_bs_return` | ✓ | `bs_not_supported_directive_in_bs_return.res` | | | `Expect_opt_in_bs_return_to_opt` | ✓ | `bs_expect_opt_in_bs_return_to_opt.res` | | -| `Misplaced_label_syntax` | ⚠ | — | bs_syntaxerr.ml:116. Only fires from `check_and_discard` in `ast_exp_apply.ml:49`, applied to the args of `->`, `#=`, `##` operators. The parser always emits those args as `Nolabel`. | | `Optional_in_uncurried_bs_attribute` | ✓ | `bs_optional_in_uncurried_bs_attribute.res` | `@this` function with optional argument. | | `Bs_this_simple_pattern` | ✓ | `bs_this_simple_pattern.res` | `@this` with destructured self pattern. | | `Experimental_feature_not_enabled` | ✓ | `let_unwrap_on_top_level_not_enabled.res` (and other let-unwrap variants) | Currently only `LetUnwrap` is checked. | @@ -361,7 +372,6 @@ Environment / `.cmi`-consistency errors. Source: [env.ml:57](../compiler/ml/env. | `Illegal_renaming` | ☐ (needs build harness) | — | Triggered when a `.cmi` filename and the module name inside it disagree. Reachable via `rescript.json` setups that rename the produced artefact, but not from a single-process `bsc` invocation that always writes `Module.cmi` to match the source. | | `Inconsistent_import` | ☐ (needs build harness) | — | Triggered when two `.cmi` files transitively imported by the same unit declare different CRCs for the same type. Needs an artificially-mutated build state across multiple compile invocations. | | `Missing_module` | ☐ (needs build harness) | — | `.cmi` referenced but absent from `-I` paths at compile time. The `super_errors_multi` runner pre-compiles every fixture file via `-bs-read-cmi`, so it never reaches this code path. | -| `Illegal_value_name` | ⚠ | — | env.ml:1622/1625 raises when an identifier is `"->"` or starts/contains `#`. The ReScript parser never emits such identifiers; only PPX-rewritten AST could reach the check. | --- @@ -403,160 +413,55 @@ multi-file harnesses, which never set `-ppx`. | `compiler/ml/translmod.ml` | `Fragile_pattern_in_toplevel` | ✓ | `fragile_pattern_toplevel.res` | | | `compiler/ml/transl_recmodule.ml` | `Circular_dependency` | ✓ | `recmodule_circular_dependency.res` | | | `compiler/ml/rec_check.ml` | `Illegal_letrec_expr` | ✓ | `illegal_letrec_expr.res` | | -| `compiler/ml/syntaxerr.ml` | `Variable_in_scope` | ⚠ | — | Reachable via `let f: type t. (t, 't) => t = …` (locally-abstract `t` collides with type variable `'t` during `varify_constructors`), but `Syntaxerr.error` has no registered pretty-printer, so it propagates as an uncaught `Fatal error: exception Syntaxerr.Error(_)`. The variant is live; the printer is dead. Treat as broken until either the printer is wired up or the variant is removed in favor of a proper diagnostic. | +| `compiler/ml/syntaxerr.ml` | `Variable_in_scope` | ? (live, broken printer) | — | Reachable via `let f: type t. (t, 't) => t = …` (locally-abstract `t` collides with type variable `'t` during `varify_constructors`), but `Syntaxerr.error` has no registered pretty-printer, so it propagates as an uncaught `Fatal error: exception Syntaxerr.Error(_)`. Not removed because the variant is live; the fix should wire up a printer or convert the check into a regular typed diagnostic. | | `compiler/ml/cmt_format.cppo.ml` | `Not_a_typedtree` | ☐ (needs binary harness) | — | cmt_format.cppo.ml:147. Fires when a tool reads a `.cmt` file whose first block isn't a typed tree. Reachable in principle by pointing the analyzer at an arbitrary file with a `.cmt` extension; out of scope for the source-only fixture harnesses. | | `compiler/ext/bsc_args.ml` | `Unknown` | ☐ (needs CLI harness) | — | bsc_args.ml:45. Reachable trivially via `bsc --bogus`, but the `super_errors{,_multi}` runners only pass `bsc` a fixed flag list plus the source file — they can't exercise CLI-level errors. | | `compiler/ext/bsc_args.ml` | `Missing` | ☐ (needs CLI harness) | — | Same as above: `bsc -o` (no following filename). Needs a harness that invokes `bsc` with crafted argv. | --- -## `compiler/frontend/ast_utf8_string.ml` (dead family) +## `compiler/frontend/ast_utf8_string.ml` (retained defensive family) -Source: [ast_utf8_string.ml:25](../compiler/frontend/ast_utf8_string.ml). All variants here are reached only via the legacy `{j|…|j}` delimiter, which the modern ReScript parser doesn't emit. Backtick template strings skip the transform entirely. +Source: [ast_utf8_string.ml:25](../compiler/frontend/ast_utf8_string.ml). Re-validation found these are source-unreachable for regular ReScript, but not completely dead: `transform_test` and the defensive string-transform path still raise them, and the OUnit unicode tests assert their offsets. Retained. | Variant | Status | |---|---| -| `Invalid_code_point` | ⚠ Dead | -| `Unterminated_backslash` | ⚠ Dead | -| `Invalid_hex_escape` | ⚠ Dead | -| `Invalid_unicode_escape` | ⚠ Dead | -| `Invalid_unicode_codepoint_escape` | ⚠ Dead | +| `Invalid_code_point` | ? (source-unreachable, retained defensive/test helper) | +| `Unterminated_backslash` | ? (source-unreachable, retained defensive/test helper) | +| `Invalid_hex_escape` | ? (source-unreachable, retained defensive/test helper) | +| `Invalid_unicode_escape` | ? (source-unreachable, retained defensive/test helper) | +| `Invalid_unicode_codepoint_escape` | ? (source-unreachable, retained defensive/test helper) | -## `compiler/frontend/ast_utf8_string_interp.ml` (dead family) +## `compiler/frontend/ast_utf8_string_interp.ml` (retained test family) Source: [ast_utf8_string_interp.ml:25](../compiler/frontend/ast_utf8_string_interp.ml). -`pos_error` is reached only through `check_and_transform`, whose only -caller in `compiler/` is `transform_test` — used by OUnit tests, not the -production pipeline. Modern ReScript backtick templates take the -`BackQuotes` branch of `transform_exp` (line 311) and skip the -interpolation parser entirely. The legacy `{j|…|j}` delimiter the -parser would otherwise route here is no longer accepted by the -scanner. All variants below are unreachable from regular ReScript -source. +`pos_error` is reached through `transform_test`, which is intentionally +used by OUnit tests. Modern ReScript backtick templates take the +`BackQuotes` branch of `transform_exp` and skip the interpolation parser, +so these are source-unreachable for regular ReScript, but not completely +dead. Retained. | Variant | Status | |---|---| -| `Invalid_code_point` | ⚠ Dead | -| `Unterminated_backslash` | ⚠ Dead | -| `Invalid_escape_code` | ⚠ Dead | -| `Invalid_hex_escape` | ⚠ Dead | -| `Invalid_unicode_escape` | ⚠ Dead | -| `Unterminated_variable` | ⚠ Dead | -| `Unmatched_paren` | ⚠ Dead | -| `Invalid_syntax_of_var` | ⚠ Dead | +| `Invalid_code_point` | ? (source-unreachable, retained test helper) | +| `Unterminated_backslash` | ? (source-unreachable, retained test helper) | +| `Invalid_escape_code` | ? (source-unreachable, retained test helper) | +| `Invalid_hex_escape` | ? (source-unreachable, retained test helper) | +| `Invalid_unicode_escape` | ? (source-unreachable, retained test helper) | +| `Unterminated_variable` | ? (source-unreachable, retained test helper) | +| `Unmatched_paren` | ? (source-unreachable, retained test helper) | +| `Invalid_syntax_of_var` | ? (source-unreachable, retained test helper) | --- -## Confirmed dead variants — candidates for removal - -Only variants with a concrete, source-level reason are listed. Each row -has been re-verified against the source as of this audit. Variants marked -`?` in the tables above are **not** included here — those may turn out to -be live and just hard to reproduce. - -**Verified dead by missing raise / construction site:** - -- `typecore.Variant_tags`, `typetexp.Variant_tags` — relayed via the - `Tags` exception which is declared in `ctype.ml:60` / `ctype.mli:57` - but **never raised** in `compiler/`. -- `typecore.Recursive_local_constraint` — relayed via - `Unification_recursive_abbrev`, raised only from the `Recursive_abbrev` - exception which is declared (`ctype.ml:110`, `ctype.mli:61`) but - **never raised**. -- `typecore.Invalid_interval` — needs `Ppat_interval`; **no construction - site** for that AST node in `compiler/syntax/src/`. -- `typecore.Invalid_for_of_pattern` — parser's - `normalize_for_of_pattern` (`res_core.ml:3841`) replaces every non-var, - non-`_` pattern with `Ppat_any` before the typer runs. -- `bs_syntaxerr.Conflict_bs_bs_this_bs_meth` — variant is declared but - no `Bs_syntaxerr.err _ Conflict_bs_bs_this_bs_meth` call exists in - `compiler/`. - -**Verified dead because parser doesn't produce required AST shape:** - -- `typetexp.Ill_typed_functor_application`, - `typetexp.Apply_structure_as_functor` — in the - `Longident.Lapply` branch; `Lapply` has no construction site in - the parser (`res_core.ml`). -- `bs_syntaxerr.Misplaced_label_syntax` — fires for labeled args to - `->`/`#=`/`##` operators; the parser always emits those with - `Nolabel`. -- `typedecl.Null_arity_external` — primitives parsed by - `Primitive.parse_declaration` always get the magic 20-byte - `prim_native_name` encoding, which bypasses the trigger; empty - `prim_name` is rejected earlier with "Not a valid global name". -- `ast_utf8_string.*` (Invalid_code_point, Unterminated_backslash, - Invalid_hex_escape, Invalid_unicode_escape, - Invalid_unicode_codepoint_escape) — the scanner - (`res_scanner.ml:350-417`) already validates escape sequences and - unicode code points; the transform never sees a string that would - fail its own re-validation. -- `ast_utf8_string_interp.*` (the whole module's error variants) — - `check_and_transform` is only ever called from `transform_test`, which - exists for OUnit tests, not the production pipeline. Modern ReScript - backtick templates take the `BackQuotes` branch of `transform_exp` - and skip the interpolation parser entirely. -- `typedecl.Val_in_structure` — typedecl.ml:1887 requires `pval_prim - = []` outside a signature; the parser's `external` recovery sets - `prim = []` only after emitting a syntax error, so the typechecker - never reaches the value declaration. -- `env.Illegal_value_name` — env.ml:1622/1625 rejects `"->"` and - identifiers containing `#`. The parser never produces such names; - PPX-rewritten AST is the only path that could trigger it. -- `bs_warnings.Statement_type` (warning 10) — only caller of - `check_application_result` passes `statement = false`, so the - `if statement then …` branch never fires. -- `bs_warnings.Unerasable_optional_argument` (warning 16) — - `type_function` (typecore.ml:3479) explicitly disables this warning - via `Warnings.parse_options false "-16"` before the check runs. -- `bs_warnings.Bs_uninterpreted_delimiters` (warning 108) — raised at - `bs_warnings.ml:29` for `Pconst_string` with delimiter `"js"`, but - the modern scanner has no `{js|…|js}` form and template strings tag - with `"bq"` after rewriting. - -**`Syntaxerr.Variable_in_scope` is a special case** — reachable from -`let f: type t. (t, 't) => t = …` but raised without a registered -printer, so it surfaces as `Fatal error: exception Syntaxerr.Error(_)`. -The variant is live; the diagnostic path is broken. Fix should either -wire up a printer or convert the check into a regular typed error. - -**Newly verified dead** (the variants the second-pass audit promoted -from `?` to ⚠, with the reason in the table): - -- `typecore.Label_mismatch`, `Abstract_wrong_label`, - `Incoherent_label_order` — defensive `try unify with Unify ->` paths - that are subsumed by `Wrong_name` / `Expr_type_clash` / - `Apply_wrong_label` in modern ReScript. -- `typedecl.Type_clash` — every recursive `type` shape that would reach - the failing `unify` is rejected earlier by `Cycle_in_def` / - `Recursive_abbrev`. -- `typedecl.Parameters_differ` — `check_regular` runs only on - abbreviations, and ReScript's parser produces `Cycle_in_def` for - every recursive abbreviation shape before this check. -- `typedecl.Rebind_wrong_type` — the parser refuses extension rebind - syntax that carries args or a result type, so source and target's - result types always unify trivially. -- `typedecl.Bad_fixed_type` — `is_fixed_type` checks the syntactic - manifest for an open row, and `expand_head` preserves that row; - there's no ReScript syntax that satisfies one check and fails the - other. -- `typedecl.Varying_anonymous` — needs `_` in a `type` parameter - position, which the parser doesn't accept. -- `typetexp.Unbound_type_constructor_2` — needs an inherited type - whose `Tconstr` body is a bare `Tvar`; the parser rejects - `type t = 'a` at top-level. -- `typemod.Cannot_eliminate_dependency` — `Mtype.nondep_supertype` - falls back to existential abstraction in every reachable case; - the `Not_found` branch never fires. -- `typemod.With_cannot_remove_constrained_type` — the parser only - accepts `'x`-style identifiers in `with type` param positions, so - the params are always fresh `Tvar`s and `params_are_constrained` - returns false. -- `bs_syntaxerr.Unhandled_poly_type` — the only way an external's - arrow chain gets an inline `Ptyp_poly` is via PPX-rewritten AST; - the parser misreads `'a.` inline as the deprecated `(. …)` - uncurried syntax. +## Removal audit notes + +All variants that were confirmed completely dead in this pass are listed +in **Removed in `jono/remove-dead-errors`** above and no longer appear in +the module tables. Previously flagged entries that were only unreachable +from regular source, but still possible through stale build artifacts, +PPX/malformed ASTs, or test helper APIs, were retained and reclassified. --- @@ -572,27 +477,11 @@ warnings still fire. Fixtures follow the naming convention `warning__.res` so coverage gaps stay greppable. -### Confirmed dead (no `prerr_warning` site in the compiler) +### Removed warnings -These warning constructors exist in `warnings.ml` but are never raised -anywhere in `compiler/`. They are candidates for removal. - -| Number | Variant | Reason | -|---|---|---| -| 1 | `Comment_start` | Lexer warning; modern parser doesn't emit. | -| 2 | `Comment_not_end` | Lexer warning; modern parser doesn't emit. | -| 7 | `Method_override` | OCaml class system, not exposed by ReScript. | -| 13 | `Instance_variable_override` | OCaml class system. | -| 14 | `Illegal_backslash` | Lexer-level escape warning; parser doesn't emit. | -| 15 | `Implicit_public_methods` | OCaml class system. | -| 29 | `Eol_in_string` | Lexer-level string warning. | -| 48 | `Eliminated_optional_arguments` | Declared but never raised. | -| 50 | `Bad_docstring` | Declared but never raised; also default-disabled. | -| 105 | `Bs_fragile_external` | Declared but never raised. | -| 106 | `Bs_unimplemented_primitive` | Declared but never raised. | -| 10 | `Statement_type` | Raised at typecore.ml:2052 inside `check_application_result`, but `statement` is always `false` at the only call site (typecore.ml:3983); sequence statements hit `Expr_type_clash` via `type_statement` unifying to `unit`. | -| 16 | `Unerasable_optional_argument` | Raised at typecore.ml:3526, but `type_function` (typecore.ml:3479) explicitly disables this warning before the check runs (`Warnings.parse_options false "-16"`). | -| 108 | `Bs_uninterpreted_delimiters` | Raised at bs_warnings.ml:29 for `Pconst_string` with delimiter `"js"`; the modern scanner has no `{js\|...\|js}` form and template strings don't tag with `"js"`. | +The warning constructors listed in **Removed in `jono/remove-dead-errors`** +were deleted. Their numeric warning slots remain holes; no warning number +was reused. ### Live but no fixture yet From a792a7c9ea9e3c2a6fe527464cabd1023900d09a Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Fri, 22 May 2026 16:36:36 +0000 Subject: [PATCH 14/26] Restore live typedecl rebind diagnostic Full build testing showed extension_rebind_mismatch.res exercises Rebind_wrong_type, not Rebind_mismatch. Restore the typed unification diagnostic and update the audit to mark Rebind_wrong_type live while leaving Rebind_mismatch unconfirmed. --- compiler/ml/typedecl.ml | 14 +++++++++----- tests/ERROR_VARIANTS.md | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 4e0f6d31cab..fe6abdf406d 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -38,6 +38,7 @@ type error = | Cannot_extend_private_type of Path.t | Not_extensible_type of Path.t | Extension_mismatch of Path.t * Includecore.type_mismatch list + | Rebind_wrong_type of Longident.t * Env.t * (type_expr * type_expr) list | Rebind_mismatch of Longident.t * Path.t * Path.t | Rebind_private of Longident.t | Bad_variance of int * (bool * bool * bool) * (bool * bool * bool) @@ -1645,11 +1646,8 @@ let transl_extension_constructor env type_path type_params typext_params priv else (Ctype.newconstr type_path typext_params, None) in (try Ctype.unify env cstr_res res - with Ctype.Unify _ -> - Location.raise_errorf ~loc:lid.loc - "The constructor %a has a type that is incompatible with this \ - extension" - Printtyp.longident lid.txt); + with Ctype.Unify trace -> + raise (Error (lid.loc, Rebind_wrong_type (lid.txt, env, trace)))); (* Remove "_" names from parameters used in the constructor *) (if not cdescr.cstr_generalized then let vars = Ctype.free_variables (Btype.newgenty (Ttuple args)) in @@ -2196,6 +2194,12 @@ let report_error ppf = function "does not match the definition of type" (Path.name path) (Includecore.report_type_mismatch "the type" "this extension" "definition") errs + | Rebind_wrong_type (lid, env, trace) -> + Printtyp.report_unification_error ppf env trace + (function + | ppf -> + fprintf ppf "The constructor %a@ has type" Printtyp.longident lid) + (function ppf -> fprintf ppf "but was expected to be of type") | Rebind_mismatch (lid, p, p') -> fprintf ppf "@[%s@ %a@ %s@ %s@ %s@ %s@ %s@]" "The constructor" Printtyp.longident lid "extends type" (Path.name p) diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index d9f8a00ac24..728658c82f0 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -66,8 +66,8 @@ fallbacks instead of catalogued variants. `Incoherent_label_order`, `Recursive_local_constraint`, `Invalid_interval`, `Invalid_for_of_pattern` - `typedecl`: `Type_clash`, `Parameters_differ`, - `Null_arity_external`, `Rebind_wrong_type`, `Bad_fixed_type`, - `Varying_anonymous`, `Val_in_structure` + `Null_arity_external`, `Bad_fixed_type`, `Varying_anonymous`, + `Val_in_structure` - `typemod`: `Cannot_eliminate_dependency`, `With_makes_applicative_functor_ill_typed`, `With_cannot_remove_constrained_type`, `Scoping_pack` @@ -84,11 +84,13 @@ fallbacks instead of catalogued variants. `Bs_uninterpreted_delimiters` Re-validation also found a few previously-flagged items that are not -completely dead: `includemod.Unbound_modtype_path` can still represent a -stale compiled-interface failure, `Syntaxerr.Variable_in_scope` is live -but lacks a registered printer, and the UTF-8 helper error families are -still raised by test/defensive helper entry points. Those are retained -below with updated notes. +completely dead: `typedecl.Rebind_wrong_type` is live via extension +rebinding across incompatible extension types, +`includemod.Unbound_modtype_path` can still represent a stale +compiled-interface failure, `Syntaxerr.Variable_in_scope` is live but +lacks a registered printer, and the UTF-8 helper error families are still +raised by test/defensive helper entry points. Those are retained below +with updated notes. --- @@ -166,7 +168,8 @@ Type-declaration errors. Source: [typedecl.ml:27](../compiler/ml/typedecl.ml). | `Cannot_extend_private_type` | ✓ | `cannot_extend_private_type.res` | | | `Not_extensible_type` | ✓ | `not_extensible_type.res` | | | `Extension_mismatch` | ✓ | `extension_arity_mismatch.res` | `type t<'a> = ..` extended with `type t += A(int)` — arity differs from the extensible type. | -| `Rebind_mismatch` | ✓ | `extension_rebind_mismatch.res` | Rebinding constructor into a different extensible type. | +| `Rebind_wrong_type` | ✓ | `extension_rebind_mismatch.res` | Rebinding constructor into a different extensible type fails while unifying the source constructor result with the extension target. | +| `Rebind_mismatch` | ? | — | The later declaration-shape check after `Rebind_wrong_type`; no source fixture was confirmed in this pass. | | `Rebind_private` | ✓ | `extension_rebind_private.res` | Rebinding a private extension constructor as public. | | `Bad_variance` | ✓ | `bad_variance.res`, `bad_variance_contra.res` | | | `Unavailable_type_constructor` | ☐ (needs build harness) | — | typedecl.ml:778. Requires a type path findable at parse time but missing during constraint enforcement; only cross-unit scenarios where a `.cmi` was found but later removed. | From 27e91e3659ddb8f71979ce2439cf907b1666db29 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 27 May 2026 17:29:28 +0000 Subject: [PATCH 15/26] Restore live typecore label diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproductions during PR review showed that two of the three typecore label variants removed by 52bed6c7f are still reachable from regular ReScript source: - Label_mismatch fires when a record literal without an expected type mixes fields from two different record types — disambiguation picks one type per label and the cross-type unify inside type_label_exp fails. Fixture: label_mismatch_record_literal.res. - Abstract_wrong_label fires when a multi-arg function literal has an inner argument label that doesn't match the expected arrow's label. Fixture: abstract_wrong_label.res. Restore both variants, their raise sites, and reporter cases. The third removed variant (Incoherent_label_order) stays removed — every attempt to reach it routed through Expr_type_clash via the modern arity-aware unify path. Update tests/ERROR_VARIANTS.md to mark both rows ✓ with the new fixtures and drop them from the removal list. The pattern-path catch in type_pat_aux stays removed (was genuinely dead because unify_pat_types already translates Ctype.Unify to Pattern_type_clash). --- compiler/ml/typecore.ml | 32 ++++++++++++++++--- compiler/ml/typecore.mli | 2 ++ tests/ERROR_VARIANTS.md | 10 ++++-- .../abstract_wrong_label.res.expected | 9 ++++++ ...label_mismatch_record_literal.res.expected | 11 +++++++ .../fixtures/abstract_wrong_label.res | 1 + .../label_mismatch_record_literal.res | 3 ++ 7 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/abstract_wrong_label.res.expected create mode 100644 tests/build_tests/super_errors/expected/label_mismatch_record_literal.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/abstract_wrong_label.res create mode 100644 tests/build_tests/super_errors/fixtures/label_mismatch_record_literal.res diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index a3774ffed24..b709ce1954a 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -32,6 +32,7 @@ type error = expected: int; provided: int; } + | Label_mismatch of Longident.t * (type_expr * type_expr) list | Pattern_type_clash of (type_expr * type_expr) list | Or_pattern_type_clash of Ident.t * (type_expr * type_expr) list | Multiply_bound_variable of string @@ -42,6 +43,7 @@ type error = } | Apply_non_function of type_expr | Apply_wrong_label of arg_label * type_expr + | Abstract_wrong_label of arg_label * type_expr | Label_multiply_defined of { label: string; jsx_component_info: jsx_prop_error_info option; @@ -3462,9 +3464,13 @@ and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l if separate then begin_def (); let ty_arg, ty_res = try filter_arrow ~env ~arity (instance env ty_expected) l - with Unify _ -> - raise - (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun))) + with Unify _ -> ( + match expand_head env ty_expected with + | {desc = Tarrow _} as ty -> + raise (Error (loc, env, Abstract_wrong_label (l, ty))) + | _ -> + raise + (Error (loc_fun, env, Too_many_arguments (in_function <> None, ty_fun)))) in let ty_arg = if is_optional l then ( @@ -3542,8 +3548,9 @@ and type_label_exp ~call_context create env loc ty_expected (* Generalize label information *) generalize_structure ty_arg; generalize_structure ty_res); - unify_exp_types ~context:None lid.loc env (instance_def ty_res) - (instance env ty_expected); + (try unify env (instance_def ty_res) (instance env ty_expected) + with Unify trace -> + raise (Error (lid.loc, env, Label_mismatch (lid.txt, trace)))); (* Instantiate so that we can generalize internal nodes *) let ty_arg = instance_def ty_arg in if separate then ( @@ -4555,6 +4562,13 @@ let report_error env loc ppf error = (if expected == 1 then "argument" else "arguments") (if provided < expected then " only" else "") provided + | Label_mismatch (lid, trace) -> + (* modified *) + super_report_unification_error ppf env trace + (function + | ppf -> + fprintf ppf "The record field %a@ belongs to the type" longident lid) + (function ppf -> fprintf ppf "but is mixed here with fields of type") | Pattern_type_clash trace -> (* modified *) super_report_unification_error ppf env trace @@ -4637,6 +4651,14 @@ let report_error env loc ppf error = in fprintf ppf "@[@[<2>%a@]@,This function has type: %a@]" print_message l type_expr ty + | Abstract_wrong_label (l, ty) -> + let label_mark = function + | Nolabel -> "but its first argument is not labelled" + | l -> + sprintf "but its first argument is labelled %s" (prefixed_label_name l) + in + fprintf ppf "@[@[<2>This function should have type@ %a@]@,%s@]" type_expr + ty (label_mark l) | Label_multiply_defined {label; jsx_component_info = Some jsx_component_info} -> fprintf ppf diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index cc63ed4fe72..10ff2ad8cab 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -65,6 +65,7 @@ type error = expected: int; provided: int; } + | Label_mismatch of Longident.t * (type_expr * type_expr) list | Pattern_type_clash of (type_expr * type_expr) list | Or_pattern_type_clash of Ident.t * (type_expr * type_expr) list | Multiply_bound_variable of string @@ -75,6 +76,7 @@ type error = } | Apply_non_function of type_expr | Apply_wrong_label of arg_label * type_expr + | Abstract_wrong_label of arg_label * type_expr | Label_multiply_defined of { label: string; jsx_component_info: Error_message_utils.jsx_prop_error_info option; diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 728658c82f0..f07deea6635 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -62,8 +62,7 @@ unreachable and removed. Guard sites that could still be reached by malformed PPX-produced ASTs now use direct `Location.raise_errorf` fallbacks instead of catalogued variants. -- `typecore`: `Label_mismatch`, `Abstract_wrong_label`, - `Incoherent_label_order`, `Recursive_local_constraint`, +- `typecore`: `Incoherent_label_order`, `Recursive_local_constraint`, `Invalid_interval`, `Invalid_for_of_pattern` - `typedecl`: `Type_clash`, `Parameters_differ`, `Null_arity_external`, `Bad_fixed_type`, `Varying_anonymous`, @@ -86,6 +85,11 @@ fallbacks instead of catalogued variants. Re-validation also found a few previously-flagged items that are not completely dead: `typedecl.Rebind_wrong_type` is live via extension rebinding across incompatible extension types, +`typecore.Label_mismatch` is live when a record literal without an +expected type mixes fields from two different record types (see +`label_mismatch_record_literal.res`), `typecore.Abstract_wrong_label` +is live when a multi-arg function literal has a mislabelled inner +argument (see `abstract_wrong_label.res`), `includemod.Unbound_modtype_path` can still represent a stale compiled-interface failure, `Syntaxerr.Variable_in_scope` is live but lacks a registered printer, and the UTF-8 helper error families are still @@ -110,6 +114,8 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). | `Expr_type_clash` | ✓ | many `*.res` | Most-fired expression error. Trace-shape sub-cases covered: `if_return_type_mismatch.res` (IfReturn), `maybe_unwrap_option.res` (MaybeUnwrapOption), `string_concat_non_string.res` (StringConcat), `labeled_fn_argument_type_clash.res` (FunctionArgument with explicit label), `math_operator_*.res` (MathOperator family), `ternary_branch_mismatch.res`, `switch_different_types.res`, `try_catch_same_type.res`, `comparison_operator.res`, `array_item_type_mismatch.res`, `array_literal_passed_to_tuple.res`, `if_condition_mismatch.res`, `while_condition.res`, `for_loop_condition.res`, `assert_condition.res`, `function_call_mismatch.res`, `awaiting_non_promise.res`, multiple `jsx_*` fixtures. | | `Apply_non_function` | ✓ | `apply_non_function.res` | | | `Apply_wrong_label` | ✓ | `apply_wrong_label.res` | | +| `Abstract_wrong_label` | ✓ | `abstract_wrong_label.res` | Multi-arg function literal where an inner argument label doesn't match the expected arrow's label (e.g. `let f: (~a, ~b) => int = (~a, ~c) => …`). | +| `Label_mismatch` | ✓ | `label_mismatch_record_literal.res` | Record literal without expected type mixing fields from two different record types — disambiguation picks one type per label, and the cross-type unify fails inside `type_label_exp`. | | `Label_multiply_defined` | ✓ | `label_multiply_defined_literal.res` | | | `Labels_missing` | ✓ | `missing_label.res`, `missing_labels.res` | | | `Label_not_mutable` | ✓ | `label_not_mutable.res` | | diff --git a/tests/build_tests/super_errors/expected/abstract_wrong_label.res.expected b/tests/build_tests/super_errors/expected/abstract_wrong_label.res.expected new file mode 100644 index 00000000000..272b06a71b5 --- /dev/null +++ b/tests/build_tests/super_errors/expected/abstract_wrong_label.res.expected @@ -0,0 +1,9 @@ + + We've found a bug for you! + /.../fixtures/abstract_wrong_label.res:1:41-52 + + 1 │ let f: (~a: int, ~b: int) => int = (~a, ~c) => a + c + 2 │ + + This function should have type (~b: int) => int + but its first argument is labelled ~c \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/label_mismatch_record_literal.res.expected b/tests/build_tests/super_errors/expected/label_mismatch_record_literal.res.expected new file mode 100644 index 00000000000..aa9a0e5d466 --- /dev/null +++ b/tests/build_tests/super_errors/expected/label_mismatch_record_literal.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/label_mismatch_record_literal.res:3:16 + + 1 │ type a = {x: int} + 2 │ type b = {y: int} + 3 │ let _ = {x: 1, y: 2} + 4 │ + + The record field y belongs to the type b + but is mixed here with fields of type a \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/abstract_wrong_label.res b/tests/build_tests/super_errors/fixtures/abstract_wrong_label.res new file mode 100644 index 00000000000..25fc0aaf18e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/abstract_wrong_label.res @@ -0,0 +1 @@ +let f: (~a: int, ~b: int) => int = (~a, ~c) => a + c diff --git a/tests/build_tests/super_errors/fixtures/label_mismatch_record_literal.res b/tests/build_tests/super_errors/fixtures/label_mismatch_record_literal.res new file mode 100644 index 00000000000..640d3ee57f7 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/label_mismatch_record_literal.res @@ -0,0 +1,3 @@ +type a = {x: int} +type b = {y: int} +let _ = {x: 1, y: 2} From 977bee00eb4526cc7158d468d0c4725428384ce6 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 27 May 2026 20:00:18 +0000 Subject: [PATCH 16/26] Switch defensive raise sites to assert false; revert typemod/Misplaced_label_syntax PR-review feedback: replacing dead raise sites with Location.raise_errorf moves code rather than removing it, and introduces a pattern that doesn't exist anywhere else in the typer modules (which use raise (Error ...) for real errors and assert false for unreachable defensive code; zero inline Location.raise_errorf calls in master). This commit aligns the cleanup with the existing convention: 1. Variants whose raise sites are confirmed unreachable from the ReScript parser have those sites rewritten to `assert false (* reason *)` with a comment citing the parser block or sibling check. The named variant (decl + reporter) stays removed. - typecore.Invalid_interval, Invalid_for_of_pattern (parser blocks / normalizes the AST) - typetexp.Unbound_type_constructor_2, Ill_typed_functor_application, Apply_structure_as_functor (parser doesn't construct Lapply or bare-Tvar Tconstr body) - typedecl.Type_clash, Parameters_differ (Cycle_in_def fires first), Null_arity_external (Primitive.parse_declaration sets prim_native_name always), Bad_fixed_type (is_fixed_type and expand_head agree), Varying_anonymous (parser rejects `_` type params), Val_in_structure (parser rejects val outside .resi) - bs_syntaxerr.Unhandled_poly_type (parser misreads inline 'a. as deprecated uncurried syntax) - env.Illegal_value_name (parser doesn't emit '->' or # identifiers) - typecore.Incoherent_label_order: collapsed into Apply_wrong_label with a comment explaining why the original second arm was unreachable 2. Variants where reproduction confirmed they ARE reachable, or where I couldn't prove unreachability, are restored to named-variant form: - typemod.Cannot_eliminate_dependency (couldn't reproduce, but reachable from non-generative functor application paths in principle - retained conservatively) - typemod.With_makes_applicative_functor_ill_typed (couldn't reproduce - retained conservatively) - typemod.With_cannot_remove_constrained_type (REACHABLE via destructive substitution on a constrained type, fixture with_cannot_remove_constrained_type.res) - typemod.Scoping_pack (couldn't reproduce - retained conservatively) - bs_syntaxerr.Misplaced_label_syntax (REACHABLE via operator-as- identifier syntax with labelled arg, fixture misplaced_label_syntax.res) 3. tests/ERROR_VARIANTS.md updated to (a) split the Removed section into "Truly dead" and "Defensive unreachable" categories, (b) note the restored typemod variants with `?` status, (c) add covered rows for the two new fixtures. Truly-dead removals (no raise site, transitively dead, or always-false guard) are unchanged: 3 Ctype exceptions, 2 relay variants (typecore.Recursive_local_constraint, typetexp.Variant_tags), bs_syntaxerr.Conflict_bs_bs_this_bs_meth, 14 warning constructors. Validation: rg confirmed no remaining references to the removed variants. make compiler, super_errors (745 fixtures), super_errors_multi (54 fixtures) all pass. make checkformat clean. --- compiler/frontend/ast_core_type.ml | 8 +- compiler/frontend/bs_syntaxerr.ml | 7 +- compiler/frontend/bs_syntaxerr.mli | 1 + compiler/ml/env.ml | 13 +- compiler/ml/typecore.ml | 26 +++- compiler/ml/typedecl.ml | 58 ++++++--- compiler/ml/typemod.ml | 57 +++++---- compiler/ml/typemod.mli | 5 + compiler/ml/typetexp.ml | 28 +++-- tests/ERROR_VARIANTS.md | 116 ++++++++++++------ .../misplaced_label_syntax.res.expected | 9 ++ ...annot_remove_constrained_type.res.expected | 12 ++ .../fixtures/misplaced_label_syntax.res | 2 + .../with_cannot_remove_constrained_type.res | 5 + 14 files changed, 245 insertions(+), 102 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/misplaced_label_syntax.res.expected create mode 100644 tests/build_tests/super_errors/expected/with_cannot_remove_constrained_type.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/misplaced_label_syntax.res create mode 100644 tests/build_tests/super_errors/fixtures/with_cannot_remove_constrained_type.res diff --git a/compiler/frontend/ast_core_type.ml b/compiler/frontend/ast_core_type.ml index 3f056c2bb4f..f834d6f4b9e 100644 --- a/compiler/frontend/ast_core_type.ml +++ b/compiler/frontend/ast_core_type.ml @@ -136,8 +136,12 @@ let list_of_arrow (ty : t) : t * Parsetree.arg list = match ty.ptyp_desc with | Ptyp_arrow {arg; ret; arity} when arity = None || acc = [] -> aux ret (arg :: acc) - | Ptyp_poly (_, ty) -> - Location.raise_errorf ~loc:ty.ptyp_loc "Unhandled poly type" + | Ptyp_poly _ -> + (* unreachable: this would require an inline Ptyp_poly inside an + external's arrow chain. The ReScript parser misreads inline `'a.` + prefix syntax as the deprecated uncurried `(. …)` form and rejects + it, so the typer never sees the required AST shape. *) + assert false | _ -> (ty, List.rev acc) in aux ty [] diff --git a/compiler/frontend/bs_syntaxerr.ml b/compiler/frontend/bs_syntaxerr.ml index 7ef491e6f9d..7509f328f1a 100644 --- a/compiler/frontend/bs_syntaxerr.ml +++ b/compiler/frontend/bs_syntaxerr.ml @@ -46,6 +46,7 @@ type error = | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature | LetUnwrap_not_supported_in_position of [`Toplevel | `Unsupported_type] + | Misplaced_label_syntax let pp_error fmt err = Format.pp_print_string fmt @@ -86,7 +87,8 @@ let pp_error fmt err = | `Toplevel -> "`let?` is not allowed for top-level bindings." | `Unsupported_type -> "`let?` is only supported in let bindings targeting the `result` or \ - `option` type.")) + `option` type.") + | Misplaced_label_syntax -> "Label syntax is not supported in this position") type exn += Error of Location.t * error @@ -103,5 +105,4 @@ let optional_err loc (lbl : Asttypes.arg_label) = | _ -> () let err_if_label loc (lbl : Asttypes.arg_label) = - if lbl <> Nolabel then - Location.raise_errorf ~loc "Label syntax is not supported in this position" + if lbl <> Nolabel then raise (Error (loc, Misplaced_label_syntax)) diff --git a/compiler/frontend/bs_syntaxerr.mli b/compiler/frontend/bs_syntaxerr.mli index c174245fb1b..4851c3adcb5 100644 --- a/compiler/frontend/bs_syntaxerr.mli +++ b/compiler/frontend/bs_syntaxerr.mli @@ -46,6 +46,7 @@ type error = | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature | LetUnwrap_not_supported_in_position of [`Toplevel | `Unsupported_type] + | Misplaced_label_syntax val err : Location.t -> error -> 'a diff --git a/compiler/ml/env.ml b/compiler/ml/env.ml index b6e23e8def8..14664670330 100644 --- a/compiler/ml/env.ml +++ b/compiler/ml/env.ml @@ -1616,13 +1616,16 @@ and check_usage loc id warn tbl = and check_value_name name loc = (* Note: we could also check here general validity of the identifier, to protect against bad identifiers forged by -pp or - -ppx preprocessors. *) - if name = "->" then - Location.raise_errorf ~loc "'%s' is not a valid value identifier." name + -ppx preprocessors. + + Both guarded paths below are unreachable: the ReScript parser never + emits value identifiers named "->" or containing "#" — both shapes + are rejected as syntax errors. *) + ignore loc; + if name = "->" then assert false else if String.length name > 0 && name.[0] = '#' then for i = 1 to String.length name - 1 do - if name.[i] = '#' then - Location.raise_errorf ~loc "'%s' is not a valid value identifier." name + if name.[i] = '#' then assert false done and store_value ?check id decl env = diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index b709ce1954a..1d666ab142e 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -1336,8 +1336,10 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp type_pat ~explode:0 p expected_ty k (* TODO: record 'extra' to remember about interval *) | Ppat_interval _ -> - Location.raise_errorf ~loc - "Only character intervals are supported in patterns." + (* unreachable: the ReScript parser (compiler/syntax/src/res_core.ml) has + no construction site for Ppat_interval — interval patterns are OCaml + syntax not part of the ReScript grammar *) + assert false | Ppat_tuple spl -> assert (List.length spl >= 2); let spl_ann = List.map (fun p -> (p, newvar ())) spl in @@ -3088,8 +3090,11 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) Types.val_loc = loc; } env ~check:(fun s -> Warnings.Unused_for_index s) | _ -> - Location.raise_errorf ~loc:param.ppat_loc - "Invalid for...of binding: only variables and _ are allowed." + (* unreachable: the parser's normalize_for_of_pattern + (compiler/syntax/src/res_core.ml:3841) catches every non-var, + non-`_` pattern, emits a syntax error, and replaces the pattern + with Ppat_any before the typer runs *) + assert false in let body = with_depth loop_depth (fun () -> @@ -3122,8 +3127,11 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) Types.val_loc = loc; } env ~check:(fun s -> Warnings.Unused_for_index s) | _ -> - Location.raise_errorf ~loc:param.ppat_loc - "Invalid for...of binding: only variables and _ are allowed." + (* unreachable: the parser's normalize_for_of_pattern + (compiler/syntax/src/res_core.ml:3841) catches every non-var, + non-`_` pattern, emits a syntax error, and replaces the pattern + with Ppat_any before the typer runs *) + assert false in let body = with_depth loop_depth (fun () -> @@ -3849,6 +3857,12 @@ and type_application ~context total_app env funct (sargs : sargs) : (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, funct.exp_type))) else + (* Originally split between Apply_wrong_label (label not in + ty_fun) and Incoherent_label_order (label in ty_fun but at + a different position). The latter is unreachable: modern + arity-aware unify in type_function eagerly compares against + ty_expected, raising Expr_type_clash before this branch + fires. Both label problems now surface as Apply_wrong_label. *) raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, ty_res))) | _ -> diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index fe6abdf406d..07ce0746225 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -108,7 +108,7 @@ let enter_type rec_flag env sdecl id = in Env.add_type ~check:true id decl env -let update_type temp_env env id loc = +let update_type temp_env env id _loc = let path = Path.Pident id in let decl = Env.find_type path temp_env in match decl.type_manifest with @@ -117,8 +117,10 @@ let update_type temp_env env id loc = let params = List.map (fun _ -> Ctype.newvar ()) decl.type_params in try Ctype.unify env (Ctype.newconstr path params) ty with Ctype.Unify _ -> - Location.raise_errorf ~loc - "This type constructor expands to an incompatible type.") + (* unreachable: every recursive abbreviation shape that would reach + this unify failure hits Cycle_in_def in check_recursive_type + (see line ~902 below) before check_coherence runs *) + assert false) (* We use the Ctype.expand_head_opt version of expand_head to get access to the manifest type of private abbreviations. *) @@ -170,7 +172,7 @@ let is_fixed_type sd = && sd.ptype_private = Private && has_row_var sty (* Set the row variable in a fixed type *) -let set_fixed_row env loc p decl = +let set_fixed_row env _loc p decl = let tm = match decl.type_manifest with | None -> assert false @@ -184,10 +186,16 @@ let set_fixed_row env loc p decl = if Btype.static_row row then Btype.newgenty Tnil else row.row_more | Tobject (ty, _) -> snd (Ctype.flatten_fields ty) | _ -> - Location.raise_errorf ~loc "This fixed type is not an object or variant" + (* unreachable: gated by `is_fixed_type decl`, which only returns true + when the syntactic manifest carries an open polymorphic-variant or + object row. `expand_head` preserves that row, so the manifest's + desc is always Tvariant or Tobject here. *) + assert false in if not (Btype.is_Tvar rv) then - Location.raise_errorf ~loc "This fixed type has no row variable"; + (* unreachable: same is_fixed_type invariant — the row is always a Tvar + (or extended-Tvar) when is_fixed_type passes *) + assert false; rv.desc <- Tconstr (p, decl.type_params, ref Mnil) (* Translate one type declaration *) @@ -978,10 +986,14 @@ let check_recursion env loc path decl to_check = match ty.desc with | Tconstr (path', args', _) -> (if Path.same path path' then ( - if not (Ctype.equal env false args args') then - Location.raise_errorf ~loc - "In the definition of %s, recursive type parameters differ." - (Path.name cpath)) + if not (Ctype.equal env false args args') then ( + (* unreachable: check_regular runs only on abbreviations, + and every recursive-abbreviation shape hits Cycle_in_def + in the earlier check_recursive_type pass before reaching + here. Variant constructors with non-uniform recursion + (`type rec t<'a> = T(t)`) don't trigger check_regular. *) + ignore cpath; + assert false)) else if (* Attempt to expand a type abbreviation if: 1- [to_check path'] holds @@ -1234,7 +1246,7 @@ let for_constr = function (fun {Types.ld_mutable; ld_type} -> (ld_mutable = Mutable, ld_type)) l -let compute_variance_gadt env check ((required, loc) as rloc) decl +let compute_variance_gadt env check ((required, _loc) as rloc) decl (tl, ret_type_opt) = match ret_type_opt with | None -> @@ -1255,9 +1267,11 @@ let compute_variance_gadt env check ((required, loc) as rloc) decl | fv :: fv2 -> (* fv1 @ fv2 = free_variables of other parameters *) if (c || n) && constrained (fv1 @ fv2) ty then - Location.raise_errorf ~loc - "In this GADT definition, the variance of some parameter \ - cannot be checked."; + (* unreachable: this would fire on a GADT parameter that's + `_` (anonymous). ReScript's parser rejects `_` in + `type t<...>` parameter positions, so the typer never + sees the required AST shape. *) + assert false; (fv :: fv1, fv2)) ([], fvl) tyl required in @@ -1882,8 +1896,12 @@ let transl_value_decl env loc valdecl = val_attributes = valdecl.pval_attributes; } | [] -> - Location.raise_errorf ~loc:valdecl.pval_loc - "Value declarations are only allowed in signatures" + (* unreachable: `pval_prim = []` outside a signature can only arise + from the parser's `external` recovery, which sets `prim = []` + *after* emitting a syntax error, so the typer never sees the + declaration. A bare `val x: int` in a .res is also rejected at + parse time. *) + assert false | _ -> let arity, from_constructor = parse_arity env valdecl.pval_type ty in let prim = Primitive.parse_declaration valdecl ~arity ~from_constructor in @@ -1897,8 +1915,12 @@ let transl_value_decl env loc valdecl = && (prim.prim_name = "" || (prim.prim_name.[0] <> '%' && prim.prim_name.[0] <> '#')) then - Location.raise_errorf ~loc:valdecl.pval_type.ptyp_loc - "External identifiers must be functions"; + (* unreachable: Primitive.parse_declaration always assigns the + magic 20-byte prim_native_name encoding to externals; the + `prim_native_name = ""` precondition can't be satisfied alongside + `prim_arity = 0` from any externals that survive parsing. Empty + prim names are rejected earlier with `Not a valid global name`. *) + assert false; { val_type = ty; val_kind = Val_prim prim; diff --git a/compiler/ml/typemod.ml b/compiler/ml/typemod.ml index 48cfba0b627..e823d4ac825 100644 --- a/compiler/ml/typemod.ml +++ b/compiler/ml/typemod.ml @@ -24,11 +24,15 @@ open Format type error = | Cannot_apply of module_type | Not_included of Includemod.error list + | Cannot_eliminate_dependency of module_type | Signature_expected | Structure_expected of module_type | With_no_component of Longident.t | With_mismatch of Longident.t * Includemod.error list + | With_makes_applicative_functor_ill_typed of + Longident.t * Path.t * Includemod.error list | With_changes_module_alias of Longident.t * Ident.t * Path.t + | With_cannot_remove_constrained_type | Repeated_name of string * string * Warnings.loc | Non_generalizable of type_expr | Non_generalizable_module of module_type @@ -36,6 +40,7 @@ type error = | Not_allowed_in_functor_body | Not_a_packed_module of type_expr | Incomplete_packed_module of type_expr + | Scoping_pack of Longident.t * type_expr | Recursive_module_require_explicit_type | Apply_generative | Cannot_scrape_alias of Path.t @@ -246,13 +251,12 @@ let check_usage_of_path_of_substituted_item paths env signature ~loc ~lid = let env = !env in try retype_applicative_functor_type ~loc env funct arg with Includemod.Error explanation -> - Location.raise_errorf ~loc - "@[@[This `with' constraint on %a makes the applicative \ - functor type %s ill-typed in the constrained \ - signature:@]@ %a@]" - Printtyp.longident lid.txt - (Path.name referenced_path) - Includemod.report_error explanation)); + raise + (Error + ( loc, + env, + With_makes_applicative_functor_ill_typed + (lid.txt, referenced_path, explanation) )))); } in iterator.Btype.it_signature iterator signature; @@ -435,11 +439,8 @@ let merge_constraint initial_env loc sg constr = in let params = tdecl.typ_type.type_params in if params_are_constrained params then - Location.raise_errorf ~loc - "@[Destructive substitutions are not supported for \ - constrained types (other than when replacing a type \ - constructor with a type constructor with the same \ - arguments).@]"; + raise + (Error (loc, initial_env, With_cannot_remove_constrained_type)); fun s path -> Subst.add_type_function path ~params ~body s in let sub = List.fold_left how_to_extend_subst Subst.identity !real_ids in @@ -1329,11 +1330,10 @@ and type_module_aux ~alias sttn funct_body anchor env smod = (Env.add_module ~arg:true param arg.mod_type env) param mty_res with Not_found -> - Location.raise_errorf ~loc:smod.pmod_loc - "@[This functor has type@ %a@ The parameter cannot be \ - eliminated in the result type.@ Bind the argument to a \ - module identifier.@]" - Printtyp.modtype mty_functor) + raise + (Error + (smod.pmod_loc, env, Cannot_eliminate_dependency mty_functor)) + ) in rm { @@ -1714,10 +1714,7 @@ let type_package env m p nl = (fun n ty -> try Ctype.unify env ty (Ctype.newvar ()) with Ctype.Unify _ -> - Location.raise_errorf ~loc:m.pmod_loc - "@[The type %a in this module cannot be exported.@ Its type \ - contains local dependencies:@ %a@]" - Printtyp.longident n Printtyp.type_expr ty) + raise (Error (m.pmod_loc, env, Scoping_pack (n, ty)))) nl tl'; (wrap_constraint env modl mty Tmodtype_implicit, tl') @@ -1833,6 +1830,16 @@ let report_error ppf = function "@[@[In this `with' constraint, the new definition of %a@ does not \ match its original definition@ in the constrained signature:@]@ %a@]" longident lid Includemod.report_error explanation + | With_makes_applicative_functor_ill_typed (lid, path, explanation) -> + fprintf ppf + "@[@[This `with' constraint on %a makes the applicative functor @ \ + type %s ill-typed in the constrained signature:@]@ %a@]" + longident lid (Path.name path) Includemod.report_error explanation + | With_cannot_remove_constrained_type -> + fprintf ppf + "@[Destructive substitutions are not supported for constrained @ \ + types (other than when replacing a type constructor with @ a type \ + constructor with the same arguments).@]" | With_changes_module_alias (lid, id, path) -> fprintf ppf "@[@[This `with' constraint on %a changes %s, which is aliased @ in \ @@ -1866,6 +1873,11 @@ let report_error ppf = function | Interface_not_compiled intf_name -> fprintf ppf "@[Could not find the .cmi file for interface@ %a.@]" Location.print_filename intf_name + | Cannot_eliminate_dependency mty -> + fprintf ppf + "@[This functor has type@ %a@ The parameter cannot be eliminated in the \ + result type.@ Bind the argument to a module identifier.@]" + modtype mty | Not_allowed_in_functor_body -> fprintf ppf "@[This expression creates fresh types.@ %s@]" "It is not allowed inside applicative functors." @@ -1875,6 +1887,9 @@ let report_error ppf = function | Incomplete_packed_module ty -> fprintf ppf "The type of this packed module contains variables:@ %a" type_expr ty + | Scoping_pack (lid, ty) -> + fprintf ppf "The type %a in this module cannot be exported.@ " longident lid; + fprintf ppf "Its type contains local dependencies:@ %a" type_expr ty | Recursive_module_require_explicit_type -> fprintf ppf "Recursive modules require an explicit module type." | Apply_generative -> diff --git a/compiler/ml/typemod.mli b/compiler/ml/typemod.mli index 8eea12e2f6b..0f36cf6afd8 100644 --- a/compiler/ml/typemod.mli +++ b/compiler/ml/typemod.mli @@ -64,11 +64,15 @@ val save_signature : type error = | Cannot_apply of module_type | Not_included of Includemod.error list + | Cannot_eliminate_dependency of module_type | Signature_expected | Structure_expected of module_type | With_no_component of Longident.t | With_mismatch of Longident.t * Includemod.error list + | With_makes_applicative_functor_ill_typed of + Longident.t * Path.t * Includemod.error list | With_changes_module_alias of Longident.t * Ident.t * Path.t + | With_cannot_remove_constrained_type | Repeated_name of string * string * Warnings.loc | Non_generalizable of type_expr | Non_generalizable_module of module_type @@ -76,6 +80,7 @@ type error = | Not_allowed_in_functor_body | Not_a_packed_module of type_expr | Incomplete_packed_module of type_expr + | Scoping_pack of Longident.t * type_expr | Recursive_module_require_explicit_type | Apply_generative | Cannot_scrape_alias of Path.t diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index c958eb4b1db..fb248c01989 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -86,8 +86,11 @@ let rec narrow_unbound_lid_error : 'a. _ -> _ -> _ -> _ -> 'a = let fmd = Env.find_module (Env.lookup_module ~load:true flid env) env in (match Env.scrape_alias env fmd.md_type with | Mty_signature _ -> - Location.raise_errorf ~loc "The module %a is a structure, not a functor" - Printtyp.longident flid + (* unreachable: this branch handles Longident.Lapply paths, but + Lapply has no construction site in compiler/syntax/src/ — ReScript's + type-level applicative-functor syntax (OCaml's M(X).t) isn't part + of the grammar *) + assert false | Mty_alias (_, p) -> raise (Error (loc, env, Cannot_scrape_alias (flid, p))) | _ -> ()); @@ -97,8 +100,8 @@ let rec narrow_unbound_lid_error : 'a. _ -> _ -> _ -> _ -> 'a = | Mty_alias (_, p) -> raise (Error (loc, env, Cannot_scrape_alias (mlid, p))) | _ -> - Location.raise_errorf ~loc "Ill-typed functor application %a" - Printtyp.longident lid)); + (* unreachable: same Lapply path as above *) + assert false)); raise (Error (loc, env, make_error lid)) let find_component (lookup : ?loc:_ -> _) make_error env loc lid = @@ -471,9 +474,13 @@ and transl_type_aux env policy styp = let row = Btype.row_repr row in row.row_fields | {desc = Tvar _}, Some (p, _) -> - Location.raise_errorf ~loc:sty.ptyp_loc - "The type constructor %a is not yet completely defined" - Printtyp.path p + (* unreachable: this requires the inherited type's body to be a + bare Tvar Tconstr, which only arises from `type 'a t = 'a`- + style declarations. ReScript syntax requires type parameters + in angle brackets (`type t<'a> = 'a`); the leading-`'a` form + is rejected at parse time *) + ignore p; + assert false | _ -> raise (Error (sty.ptyp_loc, env, Not_a_variant ty)) in List.iter @@ -617,9 +624,10 @@ and transl_fields env policy o fields = iter_add tf; OTinherit cty | {desc = Tvar _}, Some p -> - Location.raise_errorf ~loc:sty.ptyp_loc - "The type constructor %a is not yet completely defined" Printtyp.path - p + (* unreachable: same `type 'a t = 'a` shape as above; the bare-Tvar + Tconstr body can't be constructed from ReScript syntax *) + ignore p; + assert false | _ -> raise (Error (sty.ptyp_loc, env, Not_an_object t))) in let object_fields = List.map add_field fields in diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index f07deea6635..22ca2a50f34 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -57,44 +57,81 @@ dead code. ## Removed in `jono/remove-dead-errors` -The following named error and warning variants were re-validated as -unreachable and removed. Guard sites that could still be reached by -malformed PPX-produced ASTs now use direct `Location.raise_errorf` -fallbacks instead of catalogued variants. - -- `typecore`: `Incoherent_label_order`, `Recursive_local_constraint`, - `Invalid_interval`, `Invalid_for_of_pattern` -- `typedecl`: `Type_clash`, `Parameters_differ`, - `Null_arity_external`, `Bad_fixed_type`, `Varying_anonymous`, - `Val_in_structure` -- `typemod`: `Cannot_eliminate_dependency`, - `With_makes_applicative_functor_ill_typed`, - `With_cannot_remove_constrained_type`, `Scoping_pack` -- `typetexp`: `Unbound_type_constructor_2`, `Variant_tags`, - `Ill_typed_functor_application`, `Apply_structure_as_functor` -- `bs_syntaxerr`: `Conflict_bs_bs_this_bs_meth`, - `Unhandled_poly_type`, `Misplaced_label_syntax` -- `env`: `Illegal_value_name` +Two classes of removal. **Truly dead** variants — no raise site under +`compiler/`, or a guard whose predicate is always false — are deleted +entirely (declaration, raise, reporter, supporting helpers). **Defensive +unreachable** variants whose raise sites exist but are unreachable from +the ReScript parser keep their named-variant form removed and the raise +site is replaced with `assert false (* reason *)`, matching the +established convention in the typer modules (57 existing uses). + +**Truly dead — declaration + raise + reporter all removed:** + +- `ctype`: `Tags`, `Recursive_abbrev`, + `Unification_recursive_abbrev` exceptions (declared, never raised) +- `typecore`: `Recursive_local_constraint` (relay wrapper for the dead + `Unification_recursive_abbrev`) +- `typetexp`: `Variant_tags` (relay wrapper for the dead `Tags`) +- `bs_syntaxerr`: `Conflict_bs_bs_this_bs_meth` (no construction site) - `warnings`: `Comment_start`, `Comment_not_end`, `Method_override`, - `Statement_type`, `Instance_variable_override`, `Illegal_backslash`, - `Implicit_public_methods`, `Unerasable_optional_argument`, - `Eol_in_string`, `Eliminated_optional_arguments`, `Bad_docstring`, - `Bs_fragile_external`, `Bs_unimplemented_primitive`, - `Bs_uninterpreted_delimiters` - -Re-validation also found a few previously-flagged items that are not -completely dead: `typedecl.Rebind_wrong_type` is live via extension -rebinding across incompatible extension types, -`typecore.Label_mismatch` is live when a record literal without an -expected type mixes fields from two different record types (see -`label_mismatch_record_literal.res`), `typecore.Abstract_wrong_label` -is live when a multi-arg function literal has a mislabelled inner -argument (see `abstract_wrong_label.res`), -`includemod.Unbound_modtype_path` can still represent a stale -compiled-interface failure, `Syntaxerr.Variable_in_scope` is live but -lacks a registered printer, and the UTF-8 helper error families are still -raised by test/defensive helper entry points. Those are retained below -with updated notes. + `Instance_variable_override`, `Illegal_backslash`, + `Implicit_public_methods`, `Eol_in_string`, + `Eliminated_optional_arguments`, `Bad_docstring`, + `Bs_fragile_external`, `Bs_unimplemented_primitive` (declared, + never raised); `Statement_type` (caller hard-codes `statement=false`), + `Unerasable_optional_argument` (disabled around its only check), + `Bs_uninterpreted_delimiters` (trigger AST rewritten by builtin PPX + before the warning iterator runs) + +**Defensive unreachable — raise site becomes `assert false (* reason *)`, +variant + reporter removed:** + +- `typecore`: `Incoherent_label_order` (modern arity-aware unify fires + `Expr_type_clash` before this branch), `Invalid_interval` (parser + doesn't construct `Ppat_interval`), `Invalid_for_of_pattern` (parser's + `normalize_for_of_pattern` replaces non-var patterns with `Ppat_any`) +- `typetexp`: `Unbound_type_constructor_2` (needs bare-`Tvar` Tconstr + body via `type 'a t = 'a`, parser-rejected), + `Ill_typed_functor_application`, `Apply_structure_as_functor` (both + need `Longident.Lapply`, never constructed by the parser) +- `typedecl`: `Type_clash`, `Parameters_differ` (every recursive + abbreviation hits `Cycle_in_def` first), `Null_arity_external` + (`Primitive.parse_declaration` always assigns the magic + `prim_native_name` encoding), `Bad_fixed_type` (`is_fixed_type` and + `expand_head` agree on every parser-produced shape), + `Varying_anonymous` (parser rejects `_` in type parameter positions), + `Val_in_structure` (`pval_prim = []` outside a signature is only + produced by external-recovery after a syntax error) +- `bs_syntaxerr`: `Unhandled_poly_type` (parser misreads inline `'a.` + as deprecated uncurried syntax) +- `env`: `Illegal_value_name` (parser doesn't emit `"->"` or + `#`-containing identifiers) + +Re-validation found a number of previously-flagged items that are not +completely dead and have been retained as named variants: + +- `typedecl.Rebind_wrong_type` — live via extension rebinding across + incompatible extension types. +- `typecore.Label_mismatch` — live when a record literal without an + expected type mixes fields from two different record types + (`label_mismatch_record_literal.res`). +- `typecore.Abstract_wrong_label` — live when a multi-arg function + literal has a mislabelled inner argument (`abstract_wrong_label.res`). +- `typemod.With_cannot_remove_constrained_type` — live when destructive + substitution is applied to a constrained type + (`with_cannot_remove_constrained_type.res`). +- `bs_syntaxerr.Misplaced_label_syntax` — live when labelled args are + passed via operator-identifier syntax like `\"->"(x, ~b=...)` + (`misplaced_label_syntax.res`). +- `typemod.Cannot_eliminate_dependency`, `typemod.Scoping_pack`, + `typemod.With_makes_applicative_functor_ill_typed` — reproduction + attempts couldn't reach them but couldn't conclusively prove + unreachability either; retained on the conservative side. +- `includemod.Unbound_modtype_path` can still represent a stale + compiled-interface failure. +- `Syntaxerr.Variable_in_scope` is live but lacks a registered printer. +- The UTF-8 helper error families are still raised by test/defensive + helper entry points. --- @@ -202,7 +239,11 @@ Module-level errors. Source: [typemod.ml:24](../compiler/ml/typemod.ml). | `Structure_expected` | ✓ | `super_errors_multi/Smoke_unbound_module_reference` (indirect); also `open_functor.res` | | | `With_no_component` | ✓ | `with_no_component.res` | | | `With_mismatch` | ✓ | `with_mismatch.res` | | +| `With_makes_applicative_functor_ill_typed` | ? | — | typemod.ml:249. Fires when a `with` constraint on a signature containing a `Path.Papply` makes that application ill-typed. Reachable via destructive substitution on applicative functors; reproduction attempts surfaced `not a signature` first. Retained on the conservative side. | +| `With_cannot_remove_constrained_type` | ✓ | `with_cannot_remove_constrained_type.res` | Destructive substitution on a constrained type, e.g. `S with type t<'a> := 'a` where `S` has `type t<'a> constraint 'a = int`. | | `With_changes_module_alias` | ☐ (needs build harness) | — | typemod.ml:240. Fires during `with module := M2` substitution when an aliased sub-module inside the constrained signature is affected. ReScript parses `with module N := M2` (destructive substitution), but constructing a sub-module alias chain that gets invalidated requires multiple `.resi` files and a specific shape I couldn't reproduce single-file. | +| `Cannot_eliminate_dependency` | ? | — | typemod.ml:1332. Fires when `Mtype.nondep_supertype` raises `Not_found` during functor-application result-type computation. Reproduction attempts routed through `Incomplete_packed_module` or `escapes its scope`. Retained on the conservative side. | +| `Scoping_pack` | ? | — | typemod.ml:1717. Fires during first-class module packing with `with type` constraints whose constrained type isn't a free `Tvar`. Reproduction attempts routed through `Incomplete_packed_module`. Retained on the conservative side. | | `Repeated_name` | ✓ | `repeated_def_*.res` (multiple) | | | `Non_generalizable` | ✓ | `non_generalizable.res` | | | `Non_generalizable_module` | ✓ | `non_generalizable_module.res` | Nested module containing `let r = ref(None)` — the outer module's `md_type` carries the free `'_weak1` from the inner ref, so `closed_modtype` returns false and the `Sig_module` branch fires. | @@ -319,6 +360,7 @@ FFI / attribute / experimental-feature errors. Source: [bs_syntaxerr.ml:27](../c | `Bs_this_simple_pattern` | ✓ | `bs_this_simple_pattern.res` | `@this` with destructured self pattern. | | `Experimental_feature_not_enabled` | ✓ | `let_unwrap_on_top_level_not_enabled.res` (and other let-unwrap variants) | Currently only `LetUnwrap` is checked. | | `LetUnwrap_not_supported_in_position` | ✓ | `let_unwrap_on_top_level.res`, `let_unwrap_on_not_supported_variant.res` | | +| `Misplaced_label_syntax` | ✓ | `misplaced_label_syntax.res` | Labelled args passed via the operator-identifier syntax `(->)`, `(#=)`, `(##)`, e.g. `\"->"(x, ~b=...)`. | --- diff --git a/tests/build_tests/super_errors/expected/misplaced_label_syntax.res.expected b/tests/build_tests/super_errors/expected/misplaced_label_syntax.res.expected new file mode 100644 index 00000000000..552907d081f --- /dev/null +++ b/tests/build_tests/super_errors/expected/misplaced_label_syntax.res.expected @@ -0,0 +1,9 @@ + + We've found a bug for you! + /.../fixtures/misplaced_label_syntax.res:2:21-27 + + 1 │ let f = (~a, x) => x + a + 2 │ let _ = \"->"(1, ~b=f(~a=2)) + 3 │ + + Label syntax is not supported in this position \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/with_cannot_remove_constrained_type.res.expected b/tests/build_tests/super_errors/expected/with_cannot_remove_constrained_type.res.expected new file mode 100644 index 00000000000..f57ad8791db --- /dev/null +++ b/tests/build_tests/super_errors/expected/with_cannot_remove_constrained_type.res.expected @@ -0,0 +1,12 @@ + + We've found a bug for you! + /.../fixtures/with_cannot_remove_constrained_type.res:5:17-39 + + 3 │ } + 4 │ + 5 │ module type T = S with type t<'a> := 'a + 6 │ + + Destructive substitutions are not supported for constrained + types (other than when replacing a type constructor with + a type constructor with the same arguments). \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/misplaced_label_syntax.res b/tests/build_tests/super_errors/fixtures/misplaced_label_syntax.res new file mode 100644 index 00000000000..ae964b7c870 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/misplaced_label_syntax.res @@ -0,0 +1,2 @@ +let f = (~a, x) => x + a +let _ = \"->"(1, ~b=f(~a=2)) diff --git a/tests/build_tests/super_errors/fixtures/with_cannot_remove_constrained_type.res b/tests/build_tests/super_errors/fixtures/with_cannot_remove_constrained_type.res new file mode 100644 index 00000000000..4ed2a0f70ba --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/with_cannot_remove_constrained_type.res @@ -0,0 +1,5 @@ +module type S = { + type t<'a> constraint 'a = int +} + +module type T = S with type t<'a> := 'a From 5fa4a00bfb8c8e56799b43a7dfb535c69b7bf448 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 16:36:01 +0000 Subject: [PATCH 17/26] Restore reachable error variants wrongly removed as dead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-audited the defensive "unreachable" removals in this branch by driving each assert false to a real crash with a minimal .res (against a master oracle build). 8 were actually reachable or unproven and are restored: Reachable (each was a confirmed compiler crash; regression fixtures added): - typetexp.Unbound_type_constructor_2 (identity alias in inherit position) - typecore.Invalid_interval (non-char interval pattern, e.g. `1 .. 5`) - typedecl.Null_arity_external (`?`-prefixed external name, arity 0) - typedecl.Varying_anonymous (variance annotation on constrained GADT param) - typedecl.Parameters_differ (non-uniform recursion via object manifest) - typedecl.Bad_fixed_type (fully-bounded closed private polymorphic variant) - env.Illegal_value_name (escaped identifier `\"->"`) Appear dead but unproven (no structural proof, no repro found) — retained as named variants with explanatory comments at definition and raise site rather than assert false, pending further investigation: - typecore.Incoherent_label_order - typedecl.Type_clash Also restores the helpers needed by these paths (list_labels/list_labels_aux/ has_label) and updates tests/ERROR_VARIANTS.md with the corrected reachability and a re-audit correction note. The genuinely-dead removals (14 warnings, the Longident.Lapply functor paths, Tags/Variant_tags, ctype Recursive_abbrev/Unification_recursive_abbrev, Unhandled_poly_type, Conflict_bs_bs_this_bs_meth, the unescaped-"js"-delimiter path, Invalid_for_of_pattern, Val_in_structure) are kept removed. --- compiler/ml/env.ml | 19 ++--- compiler/ml/env.mli | 1 + compiler/ml/typecore.ml | 49 ++++++++--- compiler/ml/typecore.mli | 2 + compiler/ml/typedecl.ml | 80 +++++++++--------- compiler/ml/typetexp.ml | 16 ++-- compiler/ml/typetexp.mli | 1 + tests/ERROR_VARIANTS.md | 81 ++++++++++++++----- .../expected/external_null_arity.res.expected | 8 ++ .../fixed_type_no_row_variable.res.expected | 8 ++ .../gadt_varying_anonymous.res.expected | 8 ++ .../expected/illegal_value_name.res.expected | 8 ++ ...plete_type_constructor_object.res.expected | 10 +++ ..._type_constructor_polyvariant.res.expected | 10 +++ .../pattern_interval_non_char.res.expected | 11 +++ ...ursive_type_parameters_differ.res.expected | 8 ++ .../fixtures/external_null_arity.res | 1 + .../fixtures/fixed_type_no_row_variable.res | 1 + .../fixtures/gadt_varying_anonymous.res | 1 + .../fixtures/illegal_value_name.res | 1 + .../incomplete_type_constructor_object.res | 2 + ...ncomplete_type_constructor_polyvariant.res | 2 + .../fixtures/pattern_interval_non_char.res | 5 ++ .../recursive_type_parameters_differ.res | 1 + 24 files changed, 245 insertions(+), 89 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/external_null_arity.res.expected create mode 100644 tests/build_tests/super_errors/expected/fixed_type_no_row_variable.res.expected create mode 100644 tests/build_tests/super_errors/expected/gadt_varying_anonymous.res.expected create mode 100644 tests/build_tests/super_errors/expected/illegal_value_name.res.expected create mode 100644 tests/build_tests/super_errors/expected/incomplete_type_constructor_object.res.expected create mode 100644 tests/build_tests/super_errors/expected/incomplete_type_constructor_polyvariant.res.expected create mode 100644 tests/build_tests/super_errors/expected/pattern_interval_non_char.res.expected create mode 100644 tests/build_tests/super_errors/expected/recursive_type_parameters_differ.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/external_null_arity.res create mode 100644 tests/build_tests/super_errors/fixtures/fixed_type_no_row_variable.res create mode 100644 tests/build_tests/super_errors/fixtures/gadt_varying_anonymous.res create mode 100644 tests/build_tests/super_errors/fixtures/illegal_value_name.res create mode 100644 tests/build_tests/super_errors/fixtures/incomplete_type_constructor_object.res create mode 100644 tests/build_tests/super_errors/fixtures/incomplete_type_constructor_polyvariant.res create mode 100644 tests/build_tests/super_errors/fixtures/pattern_interval_non_char.res create mode 100644 tests/build_tests/super_errors/fixtures/recursive_type_parameters_differ.res diff --git a/compiler/ml/env.ml b/compiler/ml/env.ml index 14664670330..970634be03d 100644 --- a/compiler/ml/env.ml +++ b/compiler/ml/env.ml @@ -58,6 +58,7 @@ type error = | Illegal_renaming of string * string * string | Inconsistent_import of string * string * string | Missing_module of Location.t * Path.t * Path.t + | Illegal_value_name of Location.t * string exception Error of error @@ -729,6 +730,7 @@ let check_pers_struct name = Location.print_filename filename ps_name name | Inconsistent_import _ -> assert false | Missing_module _ -> assert false + | Illegal_value_name _ -> assert false in let warn = Warnings.No_cmi_file (name, Some msg) in Location.prerr_warning Location.none warn @@ -1616,16 +1618,11 @@ and check_usage loc id warn tbl = and check_value_name name loc = (* Note: we could also check here general validity of the identifier, to protect against bad identifiers forged by -pp or - -ppx preprocessors. - - Both guarded paths below are unreachable: the ReScript parser never - emits value identifiers named "->" or containing "#" — both shapes - are rejected as syntax errors. *) - ignore loc; - if name = "->" then assert false + -ppx preprocessors. *) + if name = "->" then raise (Error (Illegal_value_name (loc, name))) else if String.length name > 0 && name.[0] = '#' then for i = 1 to String.length name - 1 do - if name.[i] = '#' then assert false + if name.[i] = '#' then raise (Error (Illegal_value_name (loc, name))) done and store_value ?check id decl env = @@ -2132,9 +2129,13 @@ let report_error ppf = function fprintf ppf "@]@ @[%s@ %s@ %s.@]@]" "The compiled interface for module" (Ident.name (Path.head path2)) "was not found" + | Illegal_value_name (_loc, name) -> + fprintf ppf "'%s' is not a valid value identifier." name + let () = Location.register_error_of_exn (function - | Error (Missing_module (loc, _, _) as err) when loc <> Location.none -> + | Error ((Missing_module (loc, _, _) | Illegal_value_name (loc, _)) as err) + when loc <> Location.none -> Some (Location.error_of_printer loc report_error err) | Error err -> Some (Location.error_of_printer_file report_error err) | _ -> None) diff --git a/compiler/ml/env.mli b/compiler/ml/env.mli index 90946f94e36..48eaba1c10d 100644 --- a/compiler/ml/env.mli +++ b/compiler/ml/env.mli @@ -236,6 +236,7 @@ type error = | Illegal_renaming of string * string * string | Inconsistent_import of string * string * string | Missing_module of Location.t * Path.t * Path.t + | Illegal_value_name of Location.t * string exception Error of error diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 1d666ab142e..696ec65d762 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -64,12 +64,18 @@ type error = | Too_many_arguments of bool * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t + (* Appears to be dead: no reproduction found that reaches the raise site + (modern arity-aware application reconciles reordered labels via + unification before the positional fallback). Retained pending further + investigation rather than replaced with [assert false]. *) + | Incoherent_label_order | Less_general of string * (type_expr * type_expr) list | Modules_not_allowed | Cannot_infer_signature | Not_a_packed_module of type_expr | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string + | Invalid_interval | Invalid_for_loop_index | No_value_clauses | Exception_pattern_below_toplevel @@ -1335,11 +1341,7 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp let p = {p with ppat_loc = loc} in type_pat ~explode:0 p expected_ty k (* TODO: record 'extra' to remember about interval *) - | Ppat_interval _ -> - (* unreachable: the ReScript parser (compiler/syntax/src/res_core.ml) has - no construction site for Ppat_interval — interval patterns are OCaml - syntax not part of the ReScript grammar *) - assert false + | Ppat_interval _ -> raise (Error (loc, !env, Invalid_interval)) | Ppat_tuple spl -> assert (List.length spl >= 2); let spl_ann = List.map (fun p -> (p, newvar ())) spl in @@ -2252,6 +2254,18 @@ let is_ignore ~env ~arity funct = with Unify _ -> false) | _ -> false +let rec list_labels_aux env visited ls ty_fun = + let ty = expand_head env ty_fun in + if List.memq ty visited then (List.rev ls, false) + else + match ty.desc with + | Tarrow (arg, ty_res, _, arity) when arity = None || visited = [] -> + list_labels_aux env (ty :: visited) (arg.lbl :: ls) ty_res + | _ -> (List.rev ls, is_Tvar ty) + +let list_labels env ty = + wrap_trace_gadt_instances env (list_labels_aux env [] []) ty + let rec lower_args env seen ty_fun = let ty = expand_head env ty_fun in if List.memq ty seen then () @@ -3722,6 +3736,10 @@ and type_application ~context total_app env funct (sargs : sargs) : newty2 lv (Tarrow ({lbl = l; typ = ty}, ty_fun, Cok, None))) ty_fun omitted in + let has_label l ty_fun = + let ls, tvar = list_labels env ty_fun in + tvar || List.mem l ls + in let ignored = ref [] in let force_tvar = let t = funct.exp_type in @@ -3856,15 +3874,16 @@ and type_application ~context total_app env funct (sargs : sargs) : raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, funct.exp_type))) - else - (* Originally split between Apply_wrong_label (label not in - ty_fun) and Incoherent_label_order (label in ty_fun but at - a different position). The latter is unreachable: modern - arity-aware unify in type_function eagerly compares against - ty_expected, raising Expr_type_clash before this branch - fires. Both label problems now surface as Apply_wrong_label. *) + else if not (has_label l1 ty_fun) then raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, ty_res))) + else + (* Appears to be dead: no reproduction found that reaches this + branch (modern arity-aware unify in type_function eagerly + compares against ty_expected, so reordered labels reconcile + before reaching here). Retained pending further investigation + rather than replaced with [assert false]. *) + raise (Error (funct.exp_loc, env, Incoherent_label_order)) | _ -> raise (Error @@ -4756,6 +4775,10 @@ let report_error env loc ppf error = type_expr ty | Not_a_variant_type lid -> fprintf ppf "The type %a@ is not a variant type" longident lid + | Incoherent_label_order -> + fprintf ppf "This labeled function is applied to arguments@ "; + fprintf ppf "in an order different from other calls.@ "; + fprintf ppf "This is only allowed when the real type is known." | Less_general (kind, trace) -> (* modified *) super_report_unification_error ppf env trace @@ -4772,6 +4795,8 @@ let report_error env loc ppf error = | Unqualified_gadt_pattern (tpath, name) -> fprintf ppf "@[The GADT constructor %s of type %a@ %s.@]" name Printtyp.path tpath "must be qualified in this pattern" + | Invalid_interval -> + fprintf ppf "@[Only character intervals are supported in patterns.@]" | Invalid_for_loop_index -> fprintf ppf "@[Invalid for-loop index: only variables and _ are allowed.@]" | No_value_clauses -> diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index 10ff2ad8cab..7ffbd8f3d45 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -97,12 +97,14 @@ type error = | Too_many_arguments of bool * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t + | Incoherent_label_order | Less_general of string * (type_expr * type_expr) list | Modules_not_allowed | Cannot_infer_signature | Not_a_packed_module of type_expr | Unexpected_existential | Unqualified_gadt_pattern of Path.t * string + | Invalid_interval | Invalid_for_loop_index | No_value_clauses | Exception_pattern_below_toplevel diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 07ce0746225..2c9d77d4d2b 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -34,6 +34,13 @@ type error = | Definition_mismatch of type_expr * Includecore.type_mismatch list | Constraint_failed of type_expr * type_expr | Inconsistent_constraint of Env.t * (type_expr * type_expr) list + (* Appears to be dead: no reproduction found that reaches the raise site + (non-uniform recursion surfaces as Parameters_differ and direct cycles + as Cycle_in_def before the coherence check clashes). Retained pending + further investigation rather than replaced with [assert false]. *) + | Type_clash of Env.t * (type_expr * type_expr) list + | Parameters_differ of Path.t * type_expr * type_expr + | Null_arity_external | Unbound_type_var of type_expr * type_declaration | Cannot_extend_private_type of Path.t | Not_extensible_type of Path.t @@ -43,7 +50,9 @@ type error = | Rebind_private of Longident.t | Bad_variance of int * (bool * bool * bool) * (bool * bool * bool) | Unavailable_type_constructor of Path.t + | Bad_fixed_type of string | Unbound_type_var_ext of type_expr * extension_constructor + | Varying_anonymous | Invalid_attribute of string | Bad_immediate_attribute | Bad_unboxed_attribute of string @@ -108,19 +117,20 @@ let enter_type rec_flag env sdecl id = in Env.add_type ~check:true id decl env -let update_type temp_env env id _loc = +let update_type temp_env env id loc = let path = Path.Pident id in let decl = Env.find_type path temp_env in match decl.type_manifest with | None -> () | Some ty -> ( let params = List.map (fun _ -> Ctype.newvar ()) decl.type_params in + (* The [Type_clash] handler appears to be dead: no reproduction found + that reaches this unify failure (non-uniform recursion surfaces as + Parameters_differ and direct cycles as Cycle_in_def first). Retained + pending further investigation rather than replaced with + [assert false]. *) try Ctype.unify env (Ctype.newconstr path params) ty - with Ctype.Unify _ -> - (* unreachable: every recursive abbreviation shape that would reach - this unify failure hits Cycle_in_def in check_recursive_type - (see line ~902 below) before check_coherence runs *) - assert false) + with Ctype.Unify trace -> raise (Error (loc, Type_clash (env, trace)))) (* We use the Ctype.expand_head_opt version of expand_head to get access to the manifest type of private abbreviations. *) @@ -172,7 +182,7 @@ let is_fixed_type sd = && sd.ptype_private = Private && has_row_var sty (* Set the row variable in a fixed type *) -let set_fixed_row env _loc p decl = +let set_fixed_row env loc p decl = let tm = match decl.type_manifest with | None -> assert false @@ -185,17 +195,10 @@ let set_fixed_row env _loc p decl = tm.desc <- Tvariant {row with row_fixed = true}; if Btype.static_row row then Btype.newgenty Tnil else row.row_more | Tobject (ty, _) -> snd (Ctype.flatten_fields ty) - | _ -> - (* unreachable: gated by `is_fixed_type decl`, which only returns true - when the syntactic manifest carries an open polymorphic-variant or - object row. `expand_head` preserves that row, so the manifest's - desc is always Tvariant or Tobject here. *) - assert false + | _ -> raise (Error (loc, Bad_fixed_type "is not an object or variant")) in if not (Btype.is_Tvar rv) then - (* unreachable: same is_fixed_type invariant — the row is always a Tvar - (or extended-Tvar) when is_fixed_type passes *) - assert false; + raise (Error (loc, Bad_fixed_type "has no row variable")); rv.desc <- Tconstr (p, decl.type_params, ref Mnil) (* Translate one type declaration *) @@ -986,14 +989,12 @@ let check_recursion env loc path decl to_check = match ty.desc with | Tconstr (path', args', _) -> (if Path.same path path' then ( - if not (Ctype.equal env false args args') then ( - (* unreachable: check_regular runs only on abbreviations, - and every recursive-abbreviation shape hits Cycle_in_def - in the earlier check_recursive_type pass before reaching - here. Variant constructors with non-uniform recursion - (`type rec t<'a> = T(t)`) don't trigger check_regular. *) - ignore cpath; - assert false)) + if not (Ctype.equal env false args args') then + raise + (Error + ( loc, + Parameters_differ (cpath, ty, Ctype.newconstr path args) + ))) else if (* Attempt to expand a type abbreviation if: 1- [to_check path'] holds @@ -1246,7 +1247,7 @@ let for_constr = function (fun {Types.ld_mutable; ld_type} -> (ld_mutable = Mutable, ld_type)) l -let compute_variance_gadt env check ((required, _loc) as rloc) decl +let compute_variance_gadt env check ((required, loc) as rloc) decl (tl, ret_type_opt) = match ret_type_opt with | None -> @@ -1267,11 +1268,7 @@ let compute_variance_gadt env check ((required, _loc) as rloc) decl | fv :: fv2 -> (* fv1 @ fv2 = free_variables of other parameters *) if (c || n) && constrained (fv1 @ fv2) ty then - (* unreachable: this would fire on a GADT parameter that's - `_` (anonymous). ReScript's parser rejects `_` in - `type t<...>` parameter positions, so the typer never - sees the required AST shape. *) - assert false; + raise (Error (loc, Varying_anonymous)); (fv :: fv1, fv2)) ([], fvl) tyl required in @@ -1914,13 +1911,7 @@ let transl_value_decl env loc valdecl = && String.unsafe_get prim_native_name 1 = '\149')) && (prim.prim_name = "" || (prim.prim_name.[0] <> '%' && prim.prim_name.[0] <> '#')) - then - (* unreachable: Primitive.parse_declaration always assigns the - magic 20-byte prim_native_name encoding to externals; the - `prim_native_name = ""` precondition can't be satisfied alongside - `prim_arity = 0` from any externals that survive parsing. Empty - prim names are rejected earlier with `Not a valid global name`. *) - assert false; + then raise (Error (valdecl.pval_type.ptyp_loc, Null_arity_external)); { val_type = ty; val_kind = Val_prim prim; @@ -2176,11 +2167,22 @@ let report_error ppf = function fprintf ppf "@[%s@ @[Type@ %a@ should be an instance of@ %a@]@]" "Constraints are not satisfied in this type." Printtyp.type_expr ty Printtyp.type_expr ty' + | Parameters_differ (path, ty, ty') -> + Printtyp.reset_and_mark_loops ty; + Printtyp.mark_loops ty'; + fprintf ppf "@[In the definition of %s, type@ %a@ should be@ %a@]" + (Path.name path) Printtyp.type_expr ty Printtyp.type_expr ty' | Inconsistent_constraint (env, trace) -> fprintf ppf "The type constraints are not consistent.@."; Printtyp.report_unification_error ppf env trace (fun ppf -> fprintf ppf "Type") (fun ppf -> fprintf ppf "is not compatible with type") + | Type_clash (env, trace) -> + Printtyp.report_unification_error ppf env trace + (function + | ppf -> fprintf ppf "This type constructor expands to type") + (function ppf -> fprintf ppf "but is used here with type") + | Null_arity_external -> fprintf ppf "External identifiers must be functions" | Unbound_type_var (ty, decl) -> ( fprintf ppf "A type variable is unbound in this type declaration"; let ty = Ctype.repr ty in @@ -2268,6 +2270,10 @@ let report_error ppf = function (variance v1) | Unavailable_type_constructor p -> fprintf ppf "The definition of type %a@ is unavailable" Printtyp.path p + | Bad_fixed_type r -> fprintf ppf "This fixed type %s" r + | Varying_anonymous -> + fprintf ppf "@[%s@ %s@ %s@]" "In this GADT definition," + "the variance of some parameter" "cannot be checked" | Bad_immediate_attribute -> fprintf ppf "@[%s@ %s@]" "Types marked with the immediate attribute must be" "non-pointer types like int or bool" diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index fb248c01989..75e6b2b38d4 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -28,6 +28,7 @@ exception Already_bound type error = | Unbound_type_variable of string | Unbound_type_constructor of Longident.t + | Unbound_type_constructor_2 of Path.t | Type_arity_mismatch of Longident.t * int * int | Type_mismatch of (type_expr * type_expr) list | Alias_type_mismatch of (type_expr * type_expr) list @@ -474,13 +475,7 @@ and transl_type_aux env policy styp = let row = Btype.row_repr row in row.row_fields | {desc = Tvar _}, Some (p, _) -> - (* unreachable: this requires the inherited type's body to be a - bare Tvar Tconstr, which only arises from `type 'a t = 'a`- - style declarations. ReScript syntax requires type parameters - in angle brackets (`type t<'a> = 'a`); the leading-`'a` form - is rejected at parse time *) - ignore p; - assert false + raise (Error (sty.ptyp_loc, env, Unbound_type_constructor_2 p)) | _ -> raise (Error (sty.ptyp_loc, env, Not_a_variant ty)) in List.iter @@ -624,10 +619,7 @@ and transl_fields env policy o fields = iter_add tf; OTinherit cty | {desc = Tvar _}, Some p -> - (* unreachable: same `type 'a t = 'a` shape as above; the bare-Tvar - Tconstr body can't be constructed from ReScript syntax *) - ignore p; - assert false + raise (Error (sty.ptyp_loc, env, Unbound_type_constructor_2 p)) | _ -> raise (Error (sty.ptyp_loc, env, Not_an_object t))) in let object_fields = List.map add_field fields in @@ -791,6 +783,8 @@ let report_error env ppf = function Format.fprintf ppf "If you wanted to write a recursive type, don't forget the `rec` in \ `type rec`@]" + | Unbound_type_constructor_2 p -> + fprintf ppf "The type constructor@ %a@ is not yet completely defined" path p | Type_arity_mismatch (lid, expected, provided) -> if expected == 0 then fprintf ppf diff --git a/compiler/ml/typetexp.mli b/compiler/ml/typetexp.mli index 565535cb093..a4d0ee95cdd 100644 --- a/compiler/ml/typetexp.mli +++ b/compiler/ml/typetexp.mli @@ -37,6 +37,7 @@ exception Already_bound type error = | Unbound_type_variable of string | Unbound_type_constructor of Longident.t + | Unbound_type_constructor_2 of Path.t | Type_arity_mismatch of Longident.t * int * int | Type_mismatch of (type_expr * type_expr) list | Alias_type_mismatch of (type_expr * type_expr) list diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 22ca2a50f34..04e9cce7b73 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -86,26 +86,28 @@ established convention in the typer modules (57 existing uses). **Defensive unreachable — raise site becomes `assert false (* reason *)`, variant + reporter removed:** -- `typecore`: `Incoherent_label_order` (modern arity-aware unify fires - `Expr_type_clash` before this branch), `Invalid_interval` (parser - doesn't construct `Ppat_interval`), `Invalid_for_of_pattern` (parser's - `normalize_for_of_pattern` replaces non-var patterns with `Ppat_any`) -- `typetexp`: `Unbound_type_constructor_2` (needs bare-`Tvar` Tconstr - body via `type 'a t = 'a`, parser-rejected), - `Ill_typed_functor_application`, `Apply_structure_as_functor` (both - need `Longident.Lapply`, never constructed by the parser) -- `typedecl`: `Type_clash`, `Parameters_differ` (every recursive - abbreviation hits `Cycle_in_def` first), `Null_arity_external` - (`Primitive.parse_declaration` always assigns the magic - `prim_native_name` encoding), `Bad_fixed_type` (`is_fixed_type` and - `expand_head` agree on every parser-produced shape), - `Varying_anonymous` (parser rejects `_` in type parameter positions), - `Val_in_structure` (`pval_prim = []` outside a signature is only - produced by external-recovery after a syntax error) -- `bs_syntaxerr`: `Unhandled_poly_type` (parser misreads inline `'a.` - as deprecated uncurried syntax) -- `env`: `Illegal_value_name` (parser doesn't emit `"->"` or - `#`-containing identifiers) +- `typecore`: `Invalid_for_of_pattern` (parser's + `normalize_for_of_pattern` replaces non-var patterns with `Ppat_any`, + so the typer only ever sees `Ppat_var`/`Ppat_any` in for-of bindings) +- `typetexp`: `Ill_typed_functor_application`, `Apply_structure_as_functor` + (both need `Longident.Lapply`, never constructed by the parser — + `compiler/syntax/src/` only builds `Lident`/`Ldot`) +- `typedecl`: `Val_in_structure` (`pval_prim = []` / a bare `val` outside + a signature is only produced by the parser's external-recovery, which + emits a syntax error and aborts before the typer runs) +- `bs_syntaxerr`: `Unhandled_poly_type` (its only raise site is in + `ast_core_type.list_of_arrow`; `Ptyp_poly` cannot appear in an + external's arrow chain — external types never route through + `parse_poly_type_expr`, and any `Ptyp_poly` in a record/object field is + an opaque arg leaf, never the recursed return type) + +> ⚠️ **Reachability re-audit correction.** An earlier pass mislabelled +> several of these as unreachable on plausible-but-untested reasoning. A +> follow-up audit (empirical: each `assert false` was driven to a real +> crash with a minimal `.res`) found **8** that are actually reachable or +> unproven, and they were restored as named variants (see the retained +> list below). The remaining entries above are backed by either a parser +> grammar fact or a structural proof, not just a guess. Re-validation found a number of previously-flagged items that are not completely dead and have been retained as named variants: @@ -120,6 +122,45 @@ completely dead and have been retained as named variants: - `typemod.With_cannot_remove_constrained_type` — live when destructive substitution is applied to a constrained type (`with_cannot_remove_constrained_type.res`). +- `typetexp.Unbound_type_constructor_2` — live when an identity type + alias `type t<'a> = 'a` is used in an inherit position with a + type-variable argument: `expand_head` collapses `t<'b>` to a bare + `Tvar` while the unexpanded repr is still `Tconstr`. Reachable from + both poly-variant inherit + (`incomplete_type_constructor_polyvariant.res`) and object spread + (`incomplete_type_constructor_object.res`). The earlier + "parser-rejected leading-`'a`" reasoning was wrong; both raise sites + reproduce as a compiler crash if removed. +- `typecore.Invalid_interval` — live: the parser **does** construct + `Ppat_interval` (`res_core.ml`, ` .. `). Only the + `Pconst_char` interval is rewritten; any other constant interval (e.g. + `1 .. 5`) reaches this branch (`pattern_interval_non_char.res`). +- `typedecl.Null_arity_external` — live: an external whose name starts + with `?` (e.g. `external x: int = "?nodeFs"`) skips the magic + `prim_native_name` encoding and reaches the typer with arity 0 + (`external_null_arity.res`). +- `typedecl.Varying_anonymous` — live: a **variance annotation** on a + GADT parameter whose return type constrains it (e.g. + `type rec t<+'a> = K(int): t`) triggers it — not `_` params + (`gadt_varying_anonymous.res`). +- `typedecl.Parameters_differ` — live: non-uniform recursion through an + object/record manifest (e.g. `type rec t<'a> = {"f": t}`) is + caught by `check_regular`, not `Cycle_in_def` + (`recursive_type_parameters_differ.res`). +- `typedecl.Bad_fixed_type` — live: a fully-bounded closed private + polymorphic variant (e.g. `type t = private [< #A | #B > #A #B]`) + satisfies `is_fixed_type` but has a static (non-`Tvar`) row + (`fixed_type_no_row_variable.res`). +- `env.Illegal_value_name` — live: escaped-identifier syntax reaches + `check_value_name` during definition; `let \"->" = 1` is rejected with + a clean diagnostic (`illegal_value_name.res`). The parser does **not** + reject `\"->"`. +- `typecore.Incoherent_label_order` and `typedecl.Type_clash` — retained + but **appear dead**: no reproduction was found that reaches either + raise site, yet neither has a structural unreachability proof (both are + live OCaml branches guarded by runtime conditions). Kept as named + variants with an explanatory comment at the definition and raise site, + pending further investigation, rather than `assert false`. - `bs_syntaxerr.Misplaced_label_syntax` — live when labelled args are passed via operator-identifier syntax like `\"->"(x, ~b=...)` (`misplaced_label_syntax.res`). diff --git a/tests/build_tests/super_errors/expected/external_null_arity.res.expected b/tests/build_tests/super_errors/expected/external_null_arity.res.expected new file mode 100644 index 00000000000..b8f73e017db --- /dev/null +++ b/tests/build_tests/super_errors/expected/external_null_arity.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/external_null_arity.res:1:13-15 + + 1 │ external x: int = "?nodeFs" + 2 │ + + External identifiers must be functions \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/fixed_type_no_row_variable.res.expected b/tests/build_tests/super_errors/expected/fixed_type_no_row_variable.res.expected new file mode 100644 index 00000000000..5de503d3c09 --- /dev/null +++ b/tests/build_tests/super_errors/expected/fixed_type_no_row_variable.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/fixed_type_no_row_variable.res:1:1-36 + + 1 │ type t = private [< #A | #B > #A #B] + 2 │ + + This fixed type has no row variable \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/gadt_varying_anonymous.res.expected b/tests/build_tests/super_errors/expected/gadt_varying_anonymous.res.expected new file mode 100644 index 00000000000..d6c699960b6 --- /dev/null +++ b/tests/build_tests/super_errors/expected/gadt_varying_anonymous.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/gadt_varying_anonymous.res:1:1-32 + + 1 │ type rec t<+'a> = K(int): t + 2 │ + + In this GADT definition, the variance of some parameter cannot be checked \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/illegal_value_name.res.expected b/tests/build_tests/super_errors/expected/illegal_value_name.res.expected new file mode 100644 index 00000000000..1833a18d160 --- /dev/null +++ b/tests/build_tests/super_errors/expected/illegal_value_name.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/illegal_value_name.res:1:5-9 + + 1 │ let \"->" = 1 + 2 │ + + '->' is not a valid value identifier. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/incomplete_type_constructor_object.res.expected b/tests/build_tests/super_errors/expected/incomplete_type_constructor_object.res.expected new file mode 100644 index 00000000000..a4f936d0664 --- /dev/null +++ b/tests/build_tests/super_errors/expected/incomplete_type_constructor_object.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/incomplete_type_constructor_object.res:2:18-22 + + 1 │ type t<'a> = 'a + 2 │ type o<'b> = {...t<'b>, "x": int} + 3 │ + + The type constructor t +is not yet completely defined \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/incomplete_type_constructor_polyvariant.res.expected b/tests/build_tests/super_errors/expected/incomplete_type_constructor_polyvariant.res.expected new file mode 100644 index 00000000000..b005967e9d9 --- /dev/null +++ b/tests/build_tests/super_errors/expected/incomplete_type_constructor_polyvariant.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/incomplete_type_constructor_polyvariant.res:2:20-24 + + 1 │ type t<'a> = 'a + 2 │ type x<'b> = [#a | t<'b>] + 3 │ + + The type constructor t +is not yet completely defined \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/pattern_interval_non_char.res.expected b/tests/build_tests/super_errors/expected/pattern_interval_non_char.res.expected new file mode 100644 index 00000000000..8048b4c37fa --- /dev/null +++ b/tests/build_tests/super_errors/expected/pattern_interval_non_char.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/pattern_interval_non_char.res:3:5-10 + + 1 │ let f = x => + 2 │ switch x { + 3 │ | 1 .. 5 => "low" + 4 │ | _ => "other" + 5 │ } + + Only character intervals are supported in patterns. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/recursive_type_parameters_differ.res.expected b/tests/build_tests/super_errors/expected/recursive_type_parameters_differ.res.expected new file mode 100644 index 00000000000..5cf943c7d8e --- /dev/null +++ b/tests/build_tests/super_errors/expected/recursive_type_parameters_differ.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/recursive_type_parameters_differ.res:1:1-30 + + 1 │ type rec t<'a> = {"f": t} + 2 │ + + In the definition of t, type t should be t<'a> \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/external_null_arity.res b/tests/build_tests/super_errors/fixtures/external_null_arity.res new file mode 100644 index 00000000000..4f15f35dc15 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/external_null_arity.res @@ -0,0 +1 @@ +external x: int = "?nodeFs" diff --git a/tests/build_tests/super_errors/fixtures/fixed_type_no_row_variable.res b/tests/build_tests/super_errors/fixtures/fixed_type_no_row_variable.res new file mode 100644 index 00000000000..d7ecd99e099 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/fixed_type_no_row_variable.res @@ -0,0 +1 @@ +type t = private [< #A | #B > #A #B] diff --git a/tests/build_tests/super_errors/fixtures/gadt_varying_anonymous.res b/tests/build_tests/super_errors/fixtures/gadt_varying_anonymous.res new file mode 100644 index 00000000000..b60c16fb5ff --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/gadt_varying_anonymous.res @@ -0,0 +1 @@ +type rec t<+'a> = K(int): t diff --git a/tests/build_tests/super_errors/fixtures/illegal_value_name.res b/tests/build_tests/super_errors/fixtures/illegal_value_name.res new file mode 100644 index 00000000000..e4d9a2153ea --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/illegal_value_name.res @@ -0,0 +1 @@ +let \"->" = 1 diff --git a/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_object.res b/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_object.res new file mode 100644 index 00000000000..dadf0f2db71 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_object.res @@ -0,0 +1,2 @@ +type t<'a> = 'a +type o<'b> = {...t<'b>, "x": int} diff --git a/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_polyvariant.res b/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_polyvariant.res new file mode 100644 index 00000000000..2c6de84986c --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/incomplete_type_constructor_polyvariant.res @@ -0,0 +1,2 @@ +type t<'a> = 'a +type x<'b> = [#a | t<'b>] diff --git a/tests/build_tests/super_errors/fixtures/pattern_interval_non_char.res b/tests/build_tests/super_errors/fixtures/pattern_interval_non_char.res new file mode 100644 index 00000000000..ebde9ef093d --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/pattern_interval_non_char.res @@ -0,0 +1,5 @@ +let f = x => + switch x { + | 1 .. 5 => "low" + | _ => "other" + } diff --git a/tests/build_tests/super_errors/fixtures/recursive_type_parameters_differ.res b/tests/build_tests/super_errors/fixtures/recursive_type_parameters_differ.res new file mode 100644 index 00000000000..403d08c0ebd --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/recursive_type_parameters_differ.res @@ -0,0 +1 @@ +type rec t<'a> = {"f": t} From a3a6278815e67a5580cc4922eda35895f3be7f1b Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 16:45:46 +0000 Subject: [PATCH 18/26] Prove Incoherent_label_order reachable; refine Type_clash rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continued the reachability investigation of the two variants that were restored-but-unproven. - typecore.Incoherent_label_order: now PROVEN reachable. A not-yet- generalized function value applied more than once with labelled args in conflicting orders (`let f = g => (g(~a=1, ~b=2), g(~b=3, ~a=4))`) reaches the leftover/tvar path in type_unknown_args (type_args reconciles labels by name, so only this path is left). Added fixture labeled_args_incoherent_order.res and replaced the "appears dead" comments with the real trigger. - typedecl.Type_clash: still no reproduction after ~37 recursive / mutual / constraint / row / object / alias shapes. update_type unifies t against t's own manifest — a type against an alpha-renamed copy of itself — which cannot head-clash; every real inconsistency is caught by Cycle_in_def / Recursive_abbrev / Parameters_differ / Constraint_failed / Type_arity_mismatch first. Kept as a named variant (not assert false) with the strengthened structural rationale, pending an airtight proof. 7 of the 8 restored variants now have reproducing fixtures. --- compiler/ml/typecore.ml | 15 ++++------ compiler/ml/typedecl.ml | 23 +++++++++------ tests/ERROR_VARIANTS.md | 28 +++++++++++++------ ...labeled_args_incoherent_order.res.expected | 10 +++++++ .../labeled_args_incoherent_order.res | 1 + 5 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/labeled_args_incoherent_order.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/labeled_args_incoherent_order.res diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 696ec65d762..a0d2b7e2a5c 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -64,10 +64,6 @@ type error = | Too_many_arguments of bool * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t - (* Appears to be dead: no reproduction found that reaches the raise site - (modern arity-aware application reconciles reordered labels via - unification before the positional fallback). Retained pending further - investigation rather than replaced with [assert false]. *) | Incoherent_label_order | Less_general of string * (type_expr * type_expr) list | Modules_not_allowed @@ -3878,11 +3874,12 @@ and type_application ~context total_app env funct (sargs : sargs) : raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, ty_res))) else - (* Appears to be dead: no reproduction found that reaches this - branch (modern arity-aware unify in type_function eagerly - compares against ty_expected, so reordered labels reconcile - before reaching here). Retained pending further investigation - rather than replaced with [assert false]. *) + (* Reached when a not-yet-generalized function value is applied + more than once with labelled arguments in conflicting orders + (e.g. `g => (g(~a, ~b), g(~b, ~a))`): the first call fixes the + arrow order, and the second reordered call lands here via the + leftover/tvar path. See + fixtures/labeled_args_incoherent_order.res. *) raise (Error (funct.exp_loc, env, Incoherent_label_order)) | _ -> raise diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 2c9d77d4d2b..2e3806fa964 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -34,10 +34,14 @@ type error = | Definition_mismatch of type_expr * Includecore.type_mismatch list | Constraint_failed of type_expr * type_expr | Inconsistent_constraint of Env.t * (type_expr * type_expr) list - (* Appears to be dead: no reproduction found that reaches the raise site - (non-uniform recursion surfaces as Parameters_differ and direct cycles - as Cycle_in_def before the coherence check clashes). Retained pending - further investigation rather than replaced with [assert false]. *) + (* Appears to be dead. Its only raise site (update_type) unifies + [t] against [t]'s own manifest — i.e. a type against an + alpha-renamed copy of itself — which cannot produce a head clash; and + every genuine inconsistency is caught by a dedicated check instead + (Cycle_in_def, Recursive_abbrev, Parameters_differ, Constraint_failed, + Type_arity_mismatch). ~37 recursive/mutual/constraint/row/object/alias + shapes were tried without reaching it. Retained as a named variant + pending an airtight proof rather than replaced with [assert false]. *) | Type_clash of Env.t * (type_expr * type_expr) list | Parameters_differ of Path.t * type_expr * type_expr | Null_arity_external @@ -124,11 +128,12 @@ let update_type temp_env env id loc = | None -> () | Some ty -> ( let params = List.map (fun _ -> Ctype.newvar ()) decl.type_params in - (* The [Type_clash] handler appears to be dead: no reproduction found - that reaches this unify failure (non-uniform recursion surfaces as - Parameters_differ and direct cycles as Cycle_in_def first). Retained - pending further investigation rather than replaced with - [assert false]. *) + (* The [Type_clash] handler appears to be dead: this unifies + [t] against [t]'s own manifest [ty], i.e. a type against + an alpha-renamed copy of itself, which cannot head-clash. Genuine + inconsistencies are caught elsewhere (Cycle_in_def, Recursive_abbrev, + Parameters_differ, ...). Retained pending an airtight proof rather than + replaced with [assert false]. *) try Ctype.unify env (Ctype.newconstr path params) ty with Ctype.Unify trace -> raise (Error (loc, Type_clash (env, trace)))) diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 04e9cce7b73..8a0ac9e0509 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -106,8 +106,12 @@ variant + reporter removed:** > follow-up audit (empirical: each `assert false` was driven to a real > crash with a minimal `.res`) found **8** that are actually reachable or > unproven, and they were restored as named variants (see the retained -> list below). The remaining entries above are backed by either a parser -> grammar fact or a structural proof, not just a guess. +> list below). **7 of the 8 now have a reproducing fixture** confirming +> they are live; the last (`typedecl.Type_clash`) resisted ~37 attempts +> and has a structural argument for unreachability but no airtight proof, +> so it is retained rather than removed. The remaining entries above are +> backed by either a parser grammar fact or a structural proof, not just a +> guess. Re-validation found a number of previously-flagged items that are not completely dead and have been retained as named variants: @@ -155,12 +159,20 @@ completely dead and have been retained as named variants: `check_value_name` during definition; `let \"->" = 1` is rejected with a clean diagnostic (`illegal_value_name.res`). The parser does **not** reject `\"->"`. -- `typecore.Incoherent_label_order` and `typedecl.Type_clash` — retained - but **appear dead**: no reproduction was found that reaches either - raise site, yet neither has a structural unreachability proof (both are - live OCaml branches guarded by runtime conditions). Kept as named - variants with an explanatory comment at the definition and raise site, - pending further investigation, rather than `assert false`. +- `typecore.Incoherent_label_order` — live: a not-yet-generalized + function value applied more than once with labelled arguments in + conflicting orders (`let f = g => (g(~a=1, ~b=2), g(~b=3, ~a=4))`) hits + the leftover/tvar path in `type_unknown_args` after the first call fixes + the arrow order (`labeled_args_incoherent_order.res`). +- `typedecl.Type_clash` — retained but **appears dead**: its only raise + site (`update_type`) unifies `t` against `t`'s own + manifest — a type against an alpha-renamed copy of itself — which cannot + head-clash, and every genuine inconsistency is caught by a dedicated + check (`Cycle_in_def`, `Recursive_abbrev`, `Parameters_differ`, + `Constraint_failed`, `Type_arity_mismatch`). ~37 recursive / mutual / + constraint / row / object / alias shapes were tried without reaching it. + Kept as a named variant with an explanatory comment at the definition + and raise site, pending an airtight proof, rather than `assert false`. - `bs_syntaxerr.Misplaced_label_syntax` — live when labelled args are passed via operator-identifier syntax like `\"->"(x, ~b=...)` (`misplaced_label_syntax.res`). diff --git a/tests/build_tests/super_errors/expected/labeled_args_incoherent_order.res.expected b/tests/build_tests/super_errors/expected/labeled_args_incoherent_order.res.expected new file mode 100644 index 00000000000..a7b057c2156 --- /dev/null +++ b/tests/build_tests/super_errors/expected/labeled_args_incoherent_order.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/labeled_args_incoherent_order.res:1:30 + + 1 │ let f = g => (g(~a=1, ~b=2), g(~b=3, ~a=4)) + 2 │ + + This labeled function is applied to arguments +in an order different from other calls. +This is only allowed when the real type is known. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/labeled_args_incoherent_order.res b/tests/build_tests/super_errors/fixtures/labeled_args_incoherent_order.res new file mode 100644 index 00000000000..76d09d7fc49 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/labeled_args_incoherent_order.res @@ -0,0 +1 @@ +let f = g => (g(~a=1, ~b=2), g(~b=3, ~a=4)) From 6b291378e8fe6f5bf92a9110268c08ec08bbf1fe Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 17:06:54 +0000 Subject: [PATCH 19/26] Fix Unhandled_poly_type assert comment to the real reason MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment claimed the parser "misreads inline `'a.` as the deprecated uncurried `(. …)` form and rejects it" — that is wrong. Validated the unreachability from scratch (structural + ~13 empirical externals): - list_of_arrow only recurses an arrow's return and is only called on an external's type annotation. The external type, and every arrow arg/return, is parsed by parse_typ_expr, which never routes to parse_poly_type_expr. - An inline `'a. …` in an external's arg/return position is a plain syntax error ("Did you forget a `=` here?"), not a misread uncurried dot. - Ptyp_poly is produced only for record/object field types and signature `val` descriptions; a field-nested poly is a non-arrow leaf that list_of_arrow stops at, never the recursed return. Conclusion (unreachable) unchanged; only the rationale is corrected. No behavioral change. ERROR_VARIANTS.md already stated the correct reason. --- compiler/frontend/ast_core_type.ml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/frontend/ast_core_type.ml b/compiler/frontend/ast_core_type.ml index f834d6f4b9e..ee05b07ef8b 100644 --- a/compiler/frontend/ast_core_type.ml +++ b/compiler/frontend/ast_core_type.ml @@ -137,10 +137,15 @@ let list_of_arrow (ty : t) : t * Parsetree.arg list = | Ptyp_arrow {arg; ret; arity} when arity = None || acc = [] -> aux ret (arg :: acc) | Ptyp_poly _ -> - (* unreachable: this would require an inline Ptyp_poly inside an - external's arrow chain. The ReScript parser misreads inline `'a.` - prefix syntax as the deprecated uncurried `(. …)` form and rejects - it, so the typer never sees the required AST shape. *) + (* unreachable: [list_of_arrow] only recurses into an arrow's return + (and is only ever called on an external's type annotation), so to get + here a [Ptyp_poly] would have to sit in an external's arg/return + position. The external type — and every arrow arg/return — is parsed + by [parse_typ_expr], which never routes to [parse_poly_type_expr]; an + inline `'a. …` there is a plain syntax error ("Did you forget a `=`"). + [Ptyp_poly] is produced only for record/object field types and + signature `val` descriptions, and a field-nested poly is a non-arrow + leaf that [list_of_arrow] stops at, never the recursed return. *) assert false | _ -> (ty, List.rev acc) in From d43d92a5fa8941f55258b41aa1cc5522dbced967 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 18:19:59 +0000 Subject: [PATCH 20/26] Restore Abstract_wrong_label to its original position The branch's earlier label-diagnostic churn left Abstract_wrong_label moved from after Too_many_arguments (master) to after Apply_wrong_label, in the typecore error type (.ml + .mli) and the report_error printer. The move is purely cosmetic (sum-type constructor order / match-arm order) and only created needless diff noise. Moved all three back to match master. --- compiler/ml/typecore.ml | 18 +++++++++--------- compiler/ml/typecore.mli | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index a0d2b7e2a5c..88370ee3298 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -43,7 +43,6 @@ type error = } | Apply_non_function of type_expr | Apply_wrong_label of arg_label * type_expr - | Abstract_wrong_label of arg_label * type_expr | Label_multiply_defined of { label: string; jsx_component_info: jsx_prop_error_info option; @@ -62,6 +61,7 @@ type error = | Not_subtype of Ctype.type_pairs * Ctype.type_pairs * Ctype.subtype_context option | Too_many_arguments of bool * type_expr + | Abstract_wrong_label of arg_label * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t | Incoherent_label_order @@ -4681,14 +4681,6 @@ let report_error env loc ppf error = in fprintf ppf "@[@[<2>%a@]@,This function has type: %a@]" print_message l type_expr ty - | Abstract_wrong_label (l, ty) -> - let label_mark = function - | Nolabel -> "but its first argument is not labelled" - | l -> - sprintf "but its first argument is labelled %s" (prefixed_label_name l) - in - fprintf ppf "@[@[<2>This function should have type@ %a@]@,%s@]" type_expr - ty (label_mark l) | Label_multiply_defined {label; jsx_component_info = Some jsx_component_info} -> fprintf ppf @@ -4761,6 +4753,14 @@ let report_error env loc ppf error = else ( fprintf ppf "@[This expression should not be a function,@ "; fprintf ppf "the expected type is@ %a@]" type_expr ty) + | Abstract_wrong_label (l, ty) -> + let label_mark = function + | Nolabel -> "but its first argument is not labelled" + | l -> + sprintf "but its first argument is labelled %s" (prefixed_label_name l) + in + fprintf ppf "@[@[<2>This function should have type@ %a@]@,%s@]" type_expr + ty (label_mark l) | Scoping_let_module (id, ty) -> fprintf ppf "This `let module' expression has type@ %a@ " type_expr ty; fprintf ppf diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index 7ffbd8f3d45..9524ac834a9 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -76,7 +76,6 @@ type error = } | Apply_non_function of type_expr | Apply_wrong_label of arg_label * type_expr - | Abstract_wrong_label of arg_label * type_expr | Label_multiply_defined of { label: string; jsx_component_info: Error_message_utils.jsx_prop_error_info option; @@ -95,6 +94,7 @@ type error = | Not_subtype of Ctype.type_pairs * Ctype.type_pairs * Ctype.subtype_context option | Too_many_arguments of bool * type_expr + | Abstract_wrong_label of arg_label * type_expr | Scoping_let_module of string * type_expr | Not_a_variant_type of Longident.t | Incoherent_label_order From 839bb3d624cbb190cf44cc9fd62858f2ac66f732 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 18:29:08 +0000 Subject: [PATCH 21/26] Remove needless line-move noise from the diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed the whole diff vs master and reverted reorderings that were not real changes, so the review shows only the actual edits in place: - typemod.ml: reverted entirely to master. Its only diff was two report_error arms (Cannot_eliminate_dependency, With_changes_module_alias) reordered — pure cosmetic churn, both still raised, no content change. - typecore.ml: moved list_labels/list_labels_aux back to their original spot (after type_approx, before check_univars) with the original comment. I had re-added them before lower_args when restoring, creating a spurious delete+add pair. - bs_syntaxerr.ml/.mli: restored to master and re-applied only the intended change in place — Misplaced_label_syntax had been relocated to the end of the error type and pp_error; now it stays in its original position and the only diff is the message typo fix ("support" -> "supported"), which the fixture already expects. env.ml/.mli were already byte-identical to master. No behavioral change; build + format clean, super_errors suite green. --- compiler/frontend/bs_syntaxerr.ml | 14 +++++++++++--- compiler/frontend/bs_syntaxerr.mli | 4 +++- compiler/ml/typecore.ml | 25 +++++++++++++------------ compiler/ml/typemod.ml | 20 ++++++++++---------- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/compiler/frontend/bs_syntaxerr.ml b/compiler/frontend/bs_syntaxerr.ml index 7509f328f1a..31d2b9a0a2d 100644 --- a/compiler/frontend/bs_syntaxerr.ml +++ b/compiler/frontend/bs_syntaxerr.ml @@ -26,11 +26,13 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates + | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal + | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type @@ -42,15 +44,19 @@ type error = *) | Not_supported_directive_in_bs_return | Expect_opt_in_bs_return_to_opt + | Misplaced_label_syntax | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature | LetUnwrap_not_supported_in_position of [`Toplevel | `Unsupported_type] - | Misplaced_label_syntax let pp_error fmt err = Format.pp_print_string fmt (match err with + | Misplaced_label_syntax -> "Label syntax is not supported in this position" + (* + let fn x = ((##) x ~hi) ~lo:1 ~hi:2 + *) | Optional_in_uncurried_bs_attribute -> "Uncurried function doesn't support optional arguments yet" | Expect_opt_in_bs_return_to_opt -> @@ -59,12 +65,15 @@ let pp_error fmt err = | Not_supported_directive_in_bs_return -> "Not supported return directive" | Illegal_attribute -> "Illegal attributes" | Unsupported_predicates -> "unsupported predicates" + | Conflict_bs_bs_this_bs_meth -> + "%@this and %@bs can not be applied at the same time" | Duplicated_bs_deriving -> "duplicate @deriving attribute" | Conflict_attributes -> "conflicting attributes " | Expect_string_literal -> "expect string literal " | Expect_int_literal -> "expect int literal " | Expect_int_or_string_or_json_literal -> "expect int, string literal or json literal {json|text here|json} " + | Unhandled_poly_type -> "Unhandled poly type" | Invalid_underscore_type_in_external -> "_ is not allowed in combination with external optional type" | Invalid_bs_string_type -> "Not a valid type for %@string" @@ -87,8 +96,7 @@ let pp_error fmt err = | `Toplevel -> "`let?` is not allowed for top-level bindings." | `Unsupported_type -> "`let?` is only supported in let bindings targeting the `result` or \ - `option` type.") - | Misplaced_label_syntax -> "Label syntax is not supported in this position") + `option` type.")) type exn += Error of Location.t * error diff --git a/compiler/frontend/bs_syntaxerr.mli b/compiler/frontend/bs_syntaxerr.mli index 4851c3adcb5..ecdaaaa0e6c 100644 --- a/compiler/frontend/bs_syntaxerr.mli +++ b/compiler/frontend/bs_syntaxerr.mli @@ -26,11 +26,13 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates + | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal + | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type @@ -42,11 +44,11 @@ type error = *) | Not_supported_directive_in_bs_return | Expect_opt_in_bs_return_to_opt + | Misplaced_label_syntax | Optional_in_uncurried_bs_attribute | Bs_this_simple_pattern | Experimental_feature_not_enabled of Experimental_features.feature | LetUnwrap_not_supported_in_position of [`Toplevel | `Unsupported_type] - | Misplaced_label_syntax val err : Location.t -> error -> 'a diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 88370ee3298..6098f7c58db 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -1989,6 +1989,19 @@ let rec type_approx env sexp = ty2 | _ -> newvar () +(* List labels in a function type, and whether return type is a variable *) +let rec list_labels_aux env visited ls ty_fun = + let ty = expand_head env ty_fun in + if List.memq ty visited then (List.rev ls, false) + else + match ty.desc with + | Tarrow (arg, ty_res, _, arity) when arity = None || visited = [] -> + list_labels_aux env (ty :: visited) (arg.lbl :: ls) ty_res + | _ -> (List.rev ls, is_Tvar ty) + +let list_labels env ty = + wrap_trace_gadt_instances env (list_labels_aux env [] []) ty + (* Check that all univars are safe in a type *) let check_univars env expans kind exp ty_expected vars = if expans && not (is_nonexpansive exp) then @@ -2250,18 +2263,6 @@ let is_ignore ~env ~arity funct = with Unify _ -> false) | _ -> false -let rec list_labels_aux env visited ls ty_fun = - let ty = expand_head env ty_fun in - if List.memq ty visited then (List.rev ls, false) - else - match ty.desc with - | Tarrow (arg, ty_res, _, arity) when arity = None || visited = [] -> - list_labels_aux env (ty :: visited) (arg.lbl :: ls) ty_res - | _ -> (List.rev ls, is_Tvar ty) - -let list_labels env ty = - wrap_trace_gadt_instances env (list_labels_aux env [] []) ty - let rec lower_args env seen ty_fun = let ty = expand_head env ty_fun in if List.memq ty seen then () diff --git a/compiler/ml/typemod.ml b/compiler/ml/typemod.ml index e823d4ac825..0932c62f347 100644 --- a/compiler/ml/typemod.ml +++ b/compiler/ml/typemod.ml @@ -1818,6 +1818,11 @@ let report_error ppf = function fprintf ppf "@[This module is not a functor; it has type@ %a@]" modtype mty | Not_included errs -> fprintf ppf "@[Signature mismatch:@ %a@]" Includemod.report_error errs + | Cannot_eliminate_dependency mty -> + fprintf ppf + "@[This functor has type@ %a@ The parameter cannot be eliminated in the \ + result type.@ Bind the argument to a module identifier.@]" + modtype mty | Signature_expected -> fprintf ppf "This module type is not a signature" | Structure_expected mty -> fprintf ppf "@[This module is not a structure; it has type@ %a" modtype mty @@ -1835,16 +1840,16 @@ let report_error ppf = function "@[@[This `with' constraint on %a makes the applicative functor @ \ type %s ill-typed in the constrained signature:@]@ %a@]" longident lid (Path.name path) Includemod.report_error explanation - | With_cannot_remove_constrained_type -> - fprintf ppf - "@[Destructive substitutions are not supported for constrained @ \ - types (other than when replacing a type constructor with @ a type \ - constructor with the same arguments).@]" | With_changes_module_alias (lid, id, path) -> fprintf ppf "@[@[This `with' constraint on %a changes %s, which is aliased @ in \ the constrained signature (as %s)@].@]" longident lid (Path.name path) (Ident.name id) + | With_cannot_remove_constrained_type -> + fprintf ppf + "@[Destructive substitutions are not supported for constrained @ \ + types (other than when replacing a type constructor with @ a type \ + constructor with the same arguments).@]" | Repeated_name (kind, name, repeated_loc) -> fprintf ppf "@[Multiple definition of the %s name %s @ at @{%a@}@ @ Names must \ @@ -1873,11 +1878,6 @@ let report_error ppf = function | Interface_not_compiled intf_name -> fprintf ppf "@[Could not find the .cmi file for interface@ %a.@]" Location.print_filename intf_name - | Cannot_eliminate_dependency mty -> - fprintf ppf - "@[This functor has type@ %a@ The parameter cannot be eliminated in the \ - result type.@ Bind the argument to a module identifier.@]" - modtype mty | Not_allowed_in_functor_body -> fprintf ppf "@[This expression creates fresh types.@ %s@]" "It is not allowed inside applicative functors." From 6e1be661aeb235296a38d10da02f4ea21f8ecdf6 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 19:14:54 +0000 Subject: [PATCH 22/26] Shorten Type_clash definition comment Keep the one-liner at the definition; the full rationale lives at the update_type raise site it points to. --- compiler/ml/typedecl.ml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 2e3806fa964..a6dfa7c95e5 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -34,14 +34,7 @@ type error = | Definition_mismatch of type_expr * Includecore.type_mismatch list | Constraint_failed of type_expr * type_expr | Inconsistent_constraint of Env.t * (type_expr * type_expr) list - (* Appears to be dead. Its only raise site (update_type) unifies - [t] against [t]'s own manifest — i.e. a type against an - alpha-renamed copy of itself — which cannot produce a head clash; and - every genuine inconsistency is caught by a dedicated check instead - (Cycle_in_def, Recursive_abbrev, Parameters_differ, Constraint_failed, - Type_arity_mismatch). ~37 recursive/mutual/constraint/row/object/alias - shapes were tried without reaching it. Retained as a named variant - pending an airtight proof rather than replaced with [assert false]. *) + (* Appears dead; retained pending proof. See update_type for details. *) | Type_clash of Env.t * (type_expr * type_expr) list | Parameters_differ of Path.t * type_expr * type_expr | Null_arity_external From ed53dcd946c289f92188b8349830ea7e474a8217 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 19:25:36 +0000 Subject: [PATCH 23/26] Drop Incoherent_label_order raise-site comment It's a live variant with a fixture (labeled_args_incoherent_order.res), so the behavior is captured by the test, not a comment. Comments are reserved for removals. The raise site is now identical to master. --- compiler/ml/typecore.ml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 6098f7c58db..b6f4f803e61 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -3874,14 +3874,7 @@ and type_application ~context total_app env funct (sargs : sargs) : else if not (has_label l1 ty_fun) then raise (Error (sarg1.pexp_loc, env, Apply_wrong_label (l1, ty_res))) - else - (* Reached when a not-yet-generalized function value is applied - more than once with labelled arguments in conflicting orders - (e.g. `g => (g(~a, ~b), g(~b, ~a))`): the first call fixes the - arrow order, and the second reordered call lands here via the - leftover/tvar path. See - fixtures/labeled_args_incoherent_order.res. *) - raise (Error (funct.exp_loc, env, Incoherent_label_order)) + else raise (Error (funct.exp_loc, env, Incoherent_label_order)) | _ -> raise (Error From e243b2c79e051044e594f7c3912a2d907153dccb Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 19:25:36 +0000 Subject: [PATCH 24/26] Restore changed catalog rows to their master table positions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 9 re-audited variants (Type_clash, Parameters_differ, Null_arity_external, Bad_fixed_type, Varying_anonymous, Invalid_interval, Incoherent_label_order, Illegal_value_name, Unbound_type_constructor_2) had their per-module table rows deleted and the info relocated to the prose list, causing line drift. Each row is now back in its master position with an in-place status update (⚠ -> ✓ + fixture; Type_clash stays ⚠ "appears dead, retained"), so the diff reads as a row-level edit instead of a move. Verified all four module tables (typecore/typedecl/env/typetexp) now match master row order; remaining gaps are only the genuinely-removed variants. --- tests/ERROR_VARIANTS.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index 8a0ac9e0509..b37e62fdcbe 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -197,6 +197,7 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). |---|---|---|---| | `Polymorphic_label` | ✓ | `polymorphic_label.res` | Pattern that instantiates a polymorphic record field: `({f: (f: int => int)}: t) =>` constrains the universal `'a` of `f: 'a. 'a => 'a` to `int => int`. | | `Constructor_arity_mismatch` | ✓ | `constructor_arity_mismatch.res`, `constructor_arity_mismatch_pattern.res`, `arity_mismatch*.res` | Triggers in both expression (4028) and pattern (1426) paths. | +| `Label_mismatch` | ✓ | `label_mismatch_record_literal.res` | Record literal without expected type mixing fields from two different record types — disambiguation picks one type per label, and the cross-type unify fails inside `type_label_exp`. | | `Pattern_type_clash` | ✓ | many `*_pattern_type_clash.res` etc. | Most-fired pattern error. Sub-case fixtures: `pattern_matching_on_option_but_value_not_option.res` and `pattern_matching_on_value_but_is_option.res` (option-vs-non-option trace), `pattern_type_clash_polyvariant.res` (polyvariant tag against concrete type), `pattern_type_clash_tuple_arity.res` (tuple arity mismatch). | | `Or_pattern_type_clash` | ✓ | `or_pattern_type_clash.res` | | | `Multiply_bound_variable` | ✓ | `multiply_bound_variable.res` | | @@ -204,8 +205,6 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). | `Expr_type_clash` | ✓ | many `*.res` | Most-fired expression error. Trace-shape sub-cases covered: `if_return_type_mismatch.res` (IfReturn), `maybe_unwrap_option.res` (MaybeUnwrapOption), `string_concat_non_string.res` (StringConcat), `labeled_fn_argument_type_clash.res` (FunctionArgument with explicit label), `math_operator_*.res` (MathOperator family), `ternary_branch_mismatch.res`, `switch_different_types.res`, `try_catch_same_type.res`, `comparison_operator.res`, `array_item_type_mismatch.res`, `array_literal_passed_to_tuple.res`, `if_condition_mismatch.res`, `while_condition.res`, `for_loop_condition.res`, `assert_condition.res`, `function_call_mismatch.res`, `awaiting_non_promise.res`, multiple `jsx_*` fixtures. | | `Apply_non_function` | ✓ | `apply_non_function.res` | | | `Apply_wrong_label` | ✓ | `apply_wrong_label.res` | | -| `Abstract_wrong_label` | ✓ | `abstract_wrong_label.res` | Multi-arg function literal where an inner argument label doesn't match the expected arrow's label (e.g. `let f: (~a, ~b) => int = (~a, ~c) => …`). | -| `Label_mismatch` | ✓ | `label_mismatch_record_literal.res` | Record literal without expected type mixing fields from two different record types — disambiguation picks one type per label, and the cross-type unify fails inside `type_label_exp`. | | `Label_multiply_defined` | ✓ | `label_multiply_defined_literal.res` | | | `Labels_missing` | ✓ | `missing_label.res`, `missing_labels.res` | | | `Label_not_mutable` | ✓ | `label_not_mutable.res` | | @@ -216,14 +215,17 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). | `Private_label` | ✓ | `private_label.res` | | | `Not_subtype` | ✓ | `subtype_*.res`, `dict_show_no_coercion.res`, etc. | | | `Too_many_arguments` | ✓ | `too_many_arguments.res`, `moreArguments*.res` | | +| `Abstract_wrong_label` | ✓ | `abstract_wrong_label.res` | Multi-arg function literal where an inner argument label doesn't match the expected arrow's label (e.g. `let f: (~a, ~b) => int = (~a, ~c) => …`). | | `Scoping_let_module` | ✓ | `scoping_let_module.res` | | | `Not_a_variant_type` | ✓ | `variant_spread_pattern_not_a_variant.res` | Pattern-level variant spread of a non-variant type. | +| `Incoherent_label_order` | ✓ | `labeled_args_incoherent_order.res` | A not-yet-generalized function value applied more than once with labelled args in conflicting orders (`g => (g(~a, ~b), g(~b, ~a))`); the reordered second call hits the leftover/tvar path in `type_unknown_args`. | | `Less_general` | ✓ | `less_general_universal.res` | | | `Modules_not_allowed` | ✓ | `super_errors_multi/Modules_not_allowed_toplevel` | Toplevel `let module(M) = …` pattern with `allow_modules=false`. | | `Cannot_infer_signature` | ✓ | `cannot_infer_signature.res` | | | `Not_a_packed_module` | ✓ | `not_a_packed_module.res` | | | `Unexpected_existential` | ✓ | `super_errors_multi/Unexpected_existential_in_let` | Destructuring GADT constructor with existential in toplevel `let`. | | `Unqualified_gadt_pattern` | ✓ | `super_errors_multi/Cross_gadt_pattern` | Only reachable via cross-module GADT disambiguation; in single-file matching the constructor would resolve before this check. | +| `Invalid_interval` | ✓ | `pattern_interval_non_char.res` | Non-char constant interval pattern (e.g. `1 .. 5`); the parser builds `Ppat_interval` and only the `Pconst_char` interval is rewritten. | | `Invalid_for_loop_index` | ✓ | `invalid_for_loop_index.res` | | | `No_value_clauses` | ✓ | `no_value_clauses.res` | | | `Exception_pattern_below_toplevel` | ✓ | `exception_pattern_below_toplevel.res` | | @@ -260,6 +262,9 @@ Type-declaration errors. Source: [typedecl.ml:27](../compiler/ml/typedecl.ml). | `Definition_mismatch` | ✓ | `definition_mismatch.res` | | | `Constraint_failed` | ✓ | `constraint_failed.res` | | | `Inconsistent_constraint` | ✓ | `inconsistent_constraint.res` | | +| `Type_clash` | ⚠ | — | Appears dead; retained pending proof. `update_type` unifies `t` against `t`'s own manifest (an alpha-renamed copy of itself), which can't head-clash; real inconsistencies hit `Cycle_in_def`/`Recursive_abbrev`/`Parameters_differ` first. ~37 shapes tried without reaching it. | +| `Parameters_differ` | ✓ | `recursive_type_parameters_differ.res` | Non-uniform recursion through an object/record manifest (`type rec t<'a> = {"f": t}`), caught by `check_regular` rather than `Cycle_in_def`. | +| `Null_arity_external` | ✓ | `external_null_arity.res` | External whose name starts with `?` (e.g. `external x: int = "?nodeFs"`) skips the magic `prim_native_name` encoding and reaches the typer with arity 0. | | `Unbound_type_var` | ✓ | `unbound_type_var.res` | | | `Cannot_extend_private_type` | ✓ | `cannot_extend_private_type.res` | | | `Not_extensible_type` | ✓ | `not_extensible_type.res` | | @@ -269,7 +274,9 @@ Type-declaration errors. Source: [typedecl.ml:27](../compiler/ml/typedecl.ml). | `Rebind_private` | ✓ | `extension_rebind_private.res` | Rebinding a private extension constructor as public. | | `Bad_variance` | ✓ | `bad_variance.res`, `bad_variance_contra.res` | | | `Unavailable_type_constructor` | ☐ (needs build harness) | — | typedecl.ml:778. Requires a type path findable at parse time but missing during constraint enforcement; only cross-unit scenarios where a `.cmi` was found but later removed. | +| `Bad_fixed_type` | ✓ | `fixed_type_no_row_variable.res` | Fully-bounded closed private polymorphic variant (`type t = private [< #A | #B > #A #B]`) satisfies `is_fixed_type` but has a static (non-`Tvar`) row. | | `Unbound_type_var_ext` | ✓ | `unbound_type_var_extension.res` | | +| `Varying_anonymous` | ✓ | `gadt_varying_anonymous.res` | Variance annotation on a GADT parameter whose return type constrains it (`type rec t<+'a> = K(int): t`). | | `Invalid_attribute` | ✓ | `invalid_attribute_not_undefined.res` | | | `Bad_immediate_attribute` | ✓ | `bad_immediate_attribute.res` | | | `Bad_unboxed_attribute` | ✓ | `bad_unboxed_attribute_abstract.res`, `bad_unboxed_attribute_mutable.res`, `bad_unboxed_attribute_many_fields.res`, `bad_unboxed_attribute_extensible.res` | All 4 sub-cases covered. | @@ -318,6 +325,7 @@ Type-expression errors. Source: [typetexp.ml:28](../compiler/ml/typetexp.ml). |---|---|---|---| | `Unbound_type_variable` | ✓ | (covered indirectly via many fixtures) | | | `Unbound_type_constructor` | ✓ | `typetexp_unbound_type_constructor.res` | | +| `Unbound_type_constructor_2` | ✓ | `incomplete_type_constructor_polyvariant.res`, `incomplete_type_constructor_object.res` | Identity alias `type t<'a> = 'a` used in an inherit position with a type-variable arg; `expand_head` collapses `t<'b>` to a bare `Tvar` while the repr stays `Tconstr`. Reachable from poly-variant inherit and object spread. | | `Type_arity_mismatch` | ✓ | `type_arity_mismatch.res` | | | `Type_mismatch` | ✓ | `typetexp_type_mismatch.res` | Type-constructor application that violates a `constraint 'a = …` on the declaration. | | `Alias_type_mismatch` | ✓ | `typetexp_alias_type_mismatch.res` | | @@ -476,6 +484,7 @@ Environment / `.cmi`-consistency errors. Source: [env.ml:57](../compiler/ml/env. | `Illegal_renaming` | ☐ (needs build harness) | — | Triggered when a `.cmi` filename and the module name inside it disagree. Reachable via `rescript.json` setups that rename the produced artefact, but not from a single-process `bsc` invocation that always writes `Module.cmi` to match the source. | | `Inconsistent_import` | ☐ (needs build harness) | — | Triggered when two `.cmi` files transitively imported by the same unit declare different CRCs for the same type. Needs an artificially-mutated build state across multiple compile invocations. | | `Missing_module` | ☐ (needs build harness) | — | `.cmi` referenced but absent from `-I` paths at compile time. The `super_errors_multi` runner pre-compiles every fixture file via `-bs-read-cmi`, so it never reaches this code path. | +| `Illegal_value_name` | ✓ | `illegal_value_name.res` | Escaped identifier reaching `check_value_name` during definition (`let \"->" = 1`); the parser does not reject `\"->"`. | --- From 635ad4b851b075e98c7c70d45665431a75a1bb48 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 19:54:50 +0000 Subject: [PATCH 25/26] Add changelog entry for dead-variant removal --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f45bdab89..01d233ee348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ - Cache OPAM env, rewatch build, and instrumented dune state in the coverage workflow. https://github.com/rescript-lang/rescript/pull/8434 - Add a multi-file fixture harness (`super_errors_multi`) for cross-module errors and warnings. https://github.com/rescript-lang/rescript/pull/8433 - Catalog every named compiler error variant in `tests/ERROR_VARIANTS.md` and add fixtures for the remaining reachable ones. https://github.com/rescript-lang/rescript/pull/8446 +- Remove dead and unreachable compiler error and warning variants; add fixtures for the ones found to be reachable. https://github.com/rescript-lang/rescript/pull/8459 # 13.0.0-alpha.4 From 890bcc0a8808f906082c1263e7caa4533995f577 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Mon, 1 Jun 2026 19:58:13 +0000 Subject: [PATCH 26/26] Re-remove dead bs_syntaxerr variants (Conflict_bs_bs_this_bs_meth, Unhandled_poly_type) A prior revert that fixed the Misplaced_label_syntax line move also restored these two dead variants. Neither is reachable: Conflict_bs_bs_this_bs_meth is never raised, and Unhandled_poly_type's only use was replaced with assert false in ast_core_type. Remove both again; keep Misplaced_label_syntax in place. --- compiler/frontend/bs_syntaxerr.ml | 5 ----- compiler/frontend/bs_syntaxerr.mli | 2 -- 2 files changed, 7 deletions(-) diff --git a/compiler/frontend/bs_syntaxerr.ml b/compiler/frontend/bs_syntaxerr.ml index 31d2b9a0a2d..80cd1b461f1 100644 --- a/compiler/frontend/bs_syntaxerr.ml +++ b/compiler/frontend/bs_syntaxerr.ml @@ -26,13 +26,11 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates - | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal - | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type @@ -65,15 +63,12 @@ let pp_error fmt err = | Not_supported_directive_in_bs_return -> "Not supported return directive" | Illegal_attribute -> "Illegal attributes" | Unsupported_predicates -> "unsupported predicates" - | Conflict_bs_bs_this_bs_meth -> - "%@this and %@bs can not be applied at the same time" | Duplicated_bs_deriving -> "duplicate @deriving attribute" | Conflict_attributes -> "conflicting attributes " | Expect_string_literal -> "expect string literal " | Expect_int_literal -> "expect int literal " | Expect_int_or_string_or_json_literal -> "expect int, string literal or json literal {json|text here|json} " - | Unhandled_poly_type -> "Unhandled poly type" | Invalid_underscore_type_in_external -> "_ is not allowed in combination with external optional type" | Invalid_bs_string_type -> "Not a valid type for %@string" diff --git a/compiler/frontend/bs_syntaxerr.mli b/compiler/frontend/bs_syntaxerr.mli index ecdaaaa0e6c..9800cdaf96f 100644 --- a/compiler/frontend/bs_syntaxerr.mli +++ b/compiler/frontend/bs_syntaxerr.mli @@ -26,13 +26,11 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray type error = | Unsupported_predicates - | Conflict_bs_bs_this_bs_meth | Duplicated_bs_deriving | Conflict_attributes | Expect_int_literal | Expect_string_literal | Expect_int_or_string_or_json_literal - | Unhandled_poly_type | Invalid_underscore_type_in_external | Invalid_bs_string_type | Invalid_bs_int_type