Skip to content

Commit 62ecbbc

Browse files
authored
Improve unclear error and warning messages (rescript-lang#8460)
* Improve non-function application error wording Saying "The function has type" right after "it's not a function" was contradictory. Use "It has type" instead. * Improve @return(*_to_opt) error message The old message was grammatically broken ("expect return type to be syntax wise `_ option` for safety") and rendered `%@return` literally. State the requirement plainly and show the option syntax. * Improve @int variant @as payload error message "expect int literal" was terse and contextless. Name the attribute and show the expected form. * Improve conflicting-attributes error message "conflicting attributes" did not say which attributes conflict. List the mutually-exclusive set (@string, @int, @ignore, @unwrap). * Render @ correctly in FFI attribute error messages These messages used "%@" where they meant "@" (e.g. "%@string", "%@unwrap", "conflicts with %@Val"). pp_print_string emits it literally, so users saw "%@". Use "@" so the attribute name reads correctly. * Improve @this simple-pattern error message Fix the "%@this" rendering and the broken grammar ("expect its pattern variable to be simple form"). State what is required and why. * Tidy the "expected a string literal" error message Capitalize and use a full sentence instead of "expect string literal " (trailing space). Kept generic since it is shared by @as payloads and @@directive. * Tidy the @as int/string/json literal error message Replace "expect int, string literal or json literal {json|text here|json}" with a plain sentence. * Tighten @this and @return wording for accuracy Verified against the raise sites: - @this (ast_uncurry_gen.ml): the self parameter must satisfy is_single_variable_pattern_conservative, i.e. a plain variable or `_`. A constant pattern also fails and `_` is allowed, so "not a destructured pattern" was imprecise. State the requirement positively. - @return (ast_external_process.ml): `nullable` and `null_undefined_to_opt` trigger this too, not just `*_to_opt` directives, so describe the option requirement generically rather than naming the directive shape. * Fix subject-verb agreement in type-params-not-supported error * Fix list grammar in @as variant annotation error * Use ReScript-style quoting for `let rec` in letrec errors Replace the OCaml-style `let rec' quoting with `let rec`. * Drop OCaml-manual references from warning messages Warnings 52 and 57 pointed users to "manual section 8.5" of the OCaml manual, which is irrelevant to ReScript. Also add a space after the comma when listing ambiguous or-pattern variables. * Improve duplicate @as annotation error wording * Improve duplicate @deriving attribute error wording * Improve unsupported @return directive error message List the supported directives instead of the terse "Not supported return directive". * Make duplicate @as message context-neutral Duplicated_bs_as also fires for record fields (e.g. inline records) and polymorphic-variant tags, not just variant cases, so the message should not say "a variant case". * Report the real attribute name in misplaced @inline warning The misplaced-@inline warning hardcoded "inline1"/"inline2" as the attribute name, leaking an internal distinction; the user wrote @inline. Use the real attribute name. * Use @-prefixed attribute names in attribute warnings Warnings 47/53/54 referred to attributes as "inline" / 'inline'; ReScript messages elsewhere use the @-prefixed form (@as, @deriving, ...). Use @inline etc. for consistency. * Name the conflicting attributes in the conflict error Conflict_attributes now carries the list of attributes involved, so the message reports all of them (e.g. "@string, @int, @unwrap") instead of the full set of every possible attribute. * Use 'switch' instead of OCaml ''match' expression' in no-value-clauses error * Say 'pattern' instead of OCaml 'matching' in multiply-bound-variable error * Clarify the fragile-pattern-in-toplevel error wording * State the rule in the invalid type-variable-name error "is not allowed in programs" was vague; type variable names cannot start with an underscore. * Fix pluralization and phrasing in type-arity-mismatch error "expects 1 argument(s), but is here applied to 2 argument(s)" -> proper singular/plural and "is given". * Clarify packed-module / GADT-pattern / existential error messages - first-class module instead of "packed module" - explain that a cross-module GADT constructor needs its module prefix - explain the existential-in-pattern error and point to switch * Backtick the type-constructor name in type-arity-mismatch error Quote the name like the other polished messages (and the same message's zero-argument branch already does). * Add changelog entry for error/warning message improvements * Capitalize two lowercase error messages for consistency Errors are sentence-case; "unsupported predicates" and the consecutive- statements syntax error started lowercase. * Cover new error-message branches flagged by codecov - Add a fixture for a non-generic type applied to arguments (covers the zero-arity branch of the type-arity-mismatch message, and the plural case). - Add an @ignore/@unwrap conflict fixture (covers those attr_name arms). - Drop the unreachable `Nothing` arm from attr_name by typing the collected list to the four real tags and coercing the single-tag result.
1 parent 77a6290 commit 62ecbbc

66 files changed

Lines changed: 175 additions & 120 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
- Refactor analysis to decouple I/O from core logic. https://github.com/rescript-lang/rescript/pull/8426
4747
- Deprecate `Stdlib_Error` and `Stdlib_Exn` modules in favor of `JsError/JsExn`. https://github.com/rescript-lang/rescript/pull/8404
4848
- Remove vendored `Json` library and use `yojson` and `lsp` library for analysis. https://github.com/rescript-lang/rescript/pull/8436
49+
- Improve clarity of various error and warning messages. https://github.com/rescript-lang/rescript/pull/8460
4950

5051
#### :house: Internal
5152

compiler/ext/warnings.ml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ let message = function
373373
Printf.sprintf "this open statement shadows the %s %s (which is later used)"
374374
kind s
375375
| Attribute_payload (a, s) ->
376-
Printf.sprintf "illegal payload for attribute '%s'.\n%s" a s
376+
Printf.sprintf "illegal payload for attribute @%s.\n%s" a s
377377
| No_cmi_file (name, None) ->
378378
"no cmi file was found in path for module " ^ name
379379
| No_cmi_file (name, Some msg) ->
@@ -383,26 +383,26 @@ let message = function
383383
Printf.sprintf
384384
"Code should not depend on the actual values of\n\
385385
this constructor's arguments. They are only for information\n\
386-
and may change in future versions. (See manual section 8.5)"
386+
and may change in future versions."
387387
| Unreachable_case ->
388388
"this match case is unreachable.\n\
389389
Consider replacing it with a refutation case '<pat> -> .'"
390390
| Misplaced_attribute attr_name ->
391-
Printf.sprintf "the %S attribute cannot appear in this context" attr_name
391+
Printf.sprintf "the @%s attribute cannot appear in this context" attr_name
392392
| Duplicated_attribute attr_name ->
393-
Printf.sprintf "the %S attribute is used more than once on this expression"
393+
Printf.sprintf "the @%s attribute is used more than once on this expression"
394394
attr_name
395395
| Ambiguous_pattern vars ->
396396
let msg =
397397
let vars = List.sort String.compare vars in
398398
match vars with
399399
| [] -> assert false
400400
| [x] -> "variable " ^ x
401-
| _ :: _ -> "variables " ^ String.concat "," vars
401+
| _ :: _ -> "variables " ^ String.concat ", " vars
402402
in
403403
Printf.sprintf
404404
"Ambiguous or-pattern variables under guard;\n\
405-
%s may match different arguments. (See manual section 8.5)"
405+
%s may match different arguments."
406406
msg
407407
| Unused_module s -> "unused module " ^ s ^ "."
408408
| Constraint_on_gadt ->

compiler/frontend/ast_attributes.ml

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,21 +161,31 @@ let process_derive_type (attrs : t) : derive_attr * t =
161161

162162
(* duplicated attributes not allowed *)
163163
let iter_process_bs_string_int_unwrap_uncurry (attrs : t) =
164-
let st = ref `Nothing in
165-
let assign v (({loc; _}, _) as attr : attr) =
166-
if !st = `Nothing then (
167-
Used_attributes.mark_used_attribute attr;
168-
st := v)
169-
else Bs_syntaxerr.err loc Conflict_attributes
164+
let attr_name = function
165+
| `String -> "string"
166+
| `Int -> "int"
167+
| `Ignore -> "ignore"
168+
| `Unwrap -> "unwrap"
170169
in
171-
Ext_list.iter attrs (fun (({txt; loc = _}, _) as attr) ->
172-
match txt with
173-
| "string" -> assign `String attr
174-
| "int" -> assign `Int attr
175-
| "ignore" -> assign `Ignore attr
176-
| "unwrap" -> assign `Unwrap attr
177-
| _ -> ());
178-
!st
170+
(* Collect all of @string/@int/@ignore/@unwrap so the conflict error can
171+
report every attribute involved, not just the first pair. *)
172+
let found : ([`String | `Int | `Ignore | `Unwrap] * _) list =
173+
Ext_list.filter_map attrs (fun (({txt; _}, _) as attr) ->
174+
match txt with
175+
| "string" -> Some (`String, attr)
176+
| "int" -> Some (`Int, attr)
177+
| "ignore" -> Some (`Ignore, attr)
178+
| "unwrap" -> Some (`Unwrap, attr)
179+
| _ -> None)
180+
in
181+
match found with
182+
| [] -> `Nothing
183+
| [(v, attr)] ->
184+
Used_attributes.mark_used_attribute attr;
185+
(v :> [`Nothing | `String | `Int | `Ignore | `Unwrap])
186+
| (_, ({loc; _}, _)) :: _ as conflicting ->
187+
Bs_syntaxerr.err loc
188+
(Conflict_attributes (List.map (fun (v, _) -> attr_name v) conflicting))
179189

180190
let iter_process_bs_string_as (attrs : t) : string option =
181191
let st = ref None in

compiler/frontend/ast_external_process.ml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ let external_desc_of_non_obj (loc : Location.t) (st : external_desc)
623623
"Ill defined attribute %@set_index (arity of 3)"
624624
| {set_index = true} ->
625625
Bs_syntaxerr.err loc
626-
(Conflict_ffi_attribute "Attribute found that conflicts with %@set_index")
626+
(Conflict_ffi_attribute "Attribute found that conflicts with @set_index")
627627
| {
628628
get_index = true;
629629
val_name = None;
@@ -649,7 +649,7 @@ let external_desc_of_non_obj (loc : Location.t) (st : external_desc)
649649
arg_type_specs_length
650650
| {get_index = true} ->
651651
Bs_syntaxerr.err loc
652-
(Conflict_ffi_attribute "Attribute found that conflicts with %@get_index")
652+
(Conflict_ffi_attribute "Attribute found that conflicts with @get_index")
653653
| {
654654
module_as_val = Some external_module_name;
655655
get_index = false;
@@ -750,7 +750,7 @@ let external_desc_of_non_obj (loc : Location.t) (st : external_desc)
750750
else Js_call {splice; name; external_module_name; scopes; tagged_template}
751751
| {call_name = Some _} ->
752752
Bs_syntaxerr.err loc
753-
(Conflict_ffi_attribute "Attribute found that conflicts with %@val")
753+
(Conflict_ffi_attribute "Attribute found that conflicts with @val")
754754
| {
755755
val_name = Some {name; source = _};
756756
external_module_name;
@@ -777,7 +777,7 @@ let external_desc_of_non_obj (loc : Location.t) (st : external_desc)
777777
Js_var {name; external_module_name; scopes}
778778
| {val_name = Some _} ->
779779
Bs_syntaxerr.err loc
780-
(Conflict_ffi_attribute "Attribute found that conflicts with %@val")
780+
(Conflict_ffi_attribute "Attribute found that conflicts with @val")
781781
| {
782782
splice;
783783
scopes;
@@ -856,7 +856,7 @@ let external_desc_of_non_obj (loc : Location.t) (st : external_desc)
856856
Js_new {name; external_module_name; splice; scopes}
857857
| {new_name = Some _} ->
858858
Bs_syntaxerr.err loc
859-
(Conflict_ffi_attribute "Attribute found that conflicts with %@new")
859+
(Conflict_ffi_attribute "Attribute found that conflicts with @new")
860860
| {
861861
set_name = Some {name; source = _};
862862
val_name = None;

compiler/frontend/bs_syntaxerr.ml

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray
2727
type error =
2828
| Unsupported_predicates
2929
| Duplicated_bs_deriving
30-
| Conflict_attributes
30+
| Conflict_attributes of string list
3131
| Expect_int_literal
3232
| Expect_string_literal
3333
| Expect_int_or_string_or_json_literal
@@ -58,28 +58,36 @@ let pp_error fmt err =
5858
| Optional_in_uncurried_bs_attribute ->
5959
"Uncurried function doesn't support optional arguments yet"
6060
| Expect_opt_in_bs_return_to_opt ->
61-
"%@return directive *_to_opt expect return type to be \n\
62-
syntax wise `_ option` for safety"
63-
| Not_supported_directive_in_bs_return -> "Not supported return directive"
61+
"This @return directive requires the external's return type to be an \
62+
option, e.g. `option<int>`."
63+
| Not_supported_directive_in_bs_return ->
64+
"Unsupported @return directive. Supported directives are `null_to_opt`, \
65+
`null_undefined_to_opt` (or `nullable`), and `identity`."
6466
| Illegal_attribute -> "Illegal attributes"
65-
| Unsupported_predicates -> "unsupported predicates"
66-
| Duplicated_bs_deriving -> "duplicate @deriving attribute"
67-
| Conflict_attributes -> "conflicting attributes "
68-
| Expect_string_literal -> "expect string literal "
69-
| Expect_int_literal -> "expect int literal "
67+
| Unsupported_predicates -> "Unsupported predicates"
68+
| Duplicated_bs_deriving ->
69+
"Duplicate @deriving attribute; a type can only have one."
70+
| Conflict_attributes names ->
71+
Printf.sprintf
72+
"Conflicting attributes: %s cannot be combined; use only one."
73+
(String.concat ", " (List.map (fun n -> "@" ^ n) names))
74+
| Expect_string_literal -> "Expected a string literal."
75+
| Expect_int_literal ->
76+
"The @as payload on an @int variant must be an integer literal, e.g. \
77+
@as(42)."
7078
| Expect_int_or_string_or_json_literal ->
71-
"expect int, string literal or json literal {json|text here|json} "
79+
"The @as payload must be an integer, string, or JSON literal."
7280
| Invalid_underscore_type_in_external ->
7381
"_ is not allowed in combination with external optional type"
74-
| Invalid_bs_string_type -> "Not a valid type for %@string"
75-
| Invalid_bs_int_type -> "Not a valid type for %@int"
82+
| Invalid_bs_string_type -> "Not a valid type for @string"
83+
| Invalid_bs_int_type -> "Not a valid type for @int"
7684
| Invalid_bs_unwrap_type ->
77-
"Not a valid type for %@unwrap. Type must be an inline variant (closed), \
85+
"Not a valid type for @unwrap. Type must be an inline variant (closed), \
7886
and\n\
7987
each constructor must have an argument."
8088
| Conflict_ffi_attribute str -> "Conflicting attributes: " ^ str
8189
| Bs_this_simple_pattern ->
82-
"%@this expect its pattern variable to be simple form"
90+
"@this expects its first parameter to be a simple variable (or `_`)."
8391
| Experimental_feature_not_enabled feature ->
8492
Printf.sprintf
8593
"Experimental feature not enabled: %s. Enable it by setting \"%s\" to \

compiler/frontend/bs_syntaxerr.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray
2727
type error =
2828
| Unsupported_predicates
2929
| Duplicated_bs_deriving
30-
| Conflict_attributes
30+
| Conflict_attributes of string list
3131
| Expect_int_literal
3232
| Expect_string_literal
3333
| Expect_int_or_string_or_json_literal

compiler/ml/ast_untagged_variants.ml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ let report_error ppf =
7373
function
7474
| InvalidVariantAsAnnotation ->
7575
fprintf ppf
76-
"A variant case annotation @as(...) must be a string or integer, \
77-
boolean, null, undefined"
78-
| Duplicated_bs_as -> fprintf ppf "duplicate @as "
76+
"A variant case annotation @as(...) must be a string, integer, boolean, \
77+
null, or undefined."
78+
| Duplicated_bs_as ->
79+
fprintf ppf "Duplicate @as annotation; only one @as is allowed here."
7980
| InvalidVariantTagAnnotation ->
8081
fprintf ppf "A variant tag annotation @tag(...) must be a string"
8182
| InvalidUntaggedVariantDefinition untagged_variant ->

compiler/ml/rec_check.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ let check_recursive_bindings valbinds =
472472
let report_error ppf = function
473473
| Illegal_letrec_expr ->
474474
Format.fprintf ppf
475-
"This kind of expression is not allowed as right-hand side of `let rec'"
475+
"This kind of expression is not allowed as right-hand side of `let rec`"
476476

477477
let () =
478478
Location.register_error_of_exn (function

compiler/ml/translattribute.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ let rec add_inline_attribute (expr : Lambda.lambda) loc attributes =
7979
| Lprim (((Pjs_fn_make _ | Pjs_fn_make_unit) as p), [e], l), _ ->
8080
Lambda.Lprim (p, [add_inline_attribute e loc attributes], l)
8181
| expr, Always_inline ->
82-
Location.prerr_warning loc (Warnings.Misplaced_attribute "inline1");
82+
Location.prerr_warning loc (Warnings.Misplaced_attribute "inline");
8383
expr
8484
| expr, Never_inline ->
85-
Location.prerr_warning loc (Warnings.Misplaced_attribute "inline2");
85+
Location.prerr_warning loc (Warnings.Misplaced_attribute "inline");
8686
expr
8787

8888
(* Get the [@inlined] attribute payload (or default if not present).

compiler/ml/translmod.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,9 @@ let transl_implementation module_name (str, cc) =
486486

487487
let report_error ppf = function
488488
| Fragile_pattern_in_toplevel ->
489-
Format.fprintf ppf "@[Such fragile pattern not allowed in the toplevel@]"
489+
Format.fprintf ppf
490+
"@[This pattern can fail to match, so it is not allowed in a top-level \
491+
`let` binding.@]"
490492

491493
let () =
492494
Location.register_error_of_exn (function

0 commit comments

Comments
 (0)