@@ -69,23 +69,58 @@ let type_expr ppf typ =
6969 Printtyp. reset_and_mark_loops typ;
7070 Printtyp. type_expr ppf typ
7171
72+ type jsx_prop_error_info = {
73+ fields : Types .label_declaration list ;
74+ props_record_path : Path .t ;
75+ jsx_type : [`Fragment | `CustomComponent | `LowercaseComponent ];
76+ }
77+
7278type type_clash_statement = FunctionCall
7379type type_clash_context =
74- | SetRecordField
80+ | SetRecordField of string (* field name *)
81+ | RecordField of {
82+ jsx : jsx_prop_error_info option ;
83+ record_type : Types .type_expr ;
84+ field_name : string ;
85+ optional : bool ;
86+ }
7587 | ArrayValue
7688 | MaybeUnwrapOption
7789 | IfCondition
90+ | AssertCondition
7891 | IfReturn
79- | Switch
92+ | SwitchReturn
93+ | TryReturn
8094 | StringConcat
8195 | ComparisonOperator
96+ | WhileCondition
8297 | MathOperator of {
8398 for_float : bool ;
8499 operator : string ;
85100 is_constant : string option ;
86101 }
87- | FunctionArgument
102+ | FunctionArgument of { optional : bool ; name : string option }
88103 | Statement of type_clash_statement
104+ | ForLoopCondition
105+
106+ let context_to_string = function
107+ | Some WhileCondition -> " WhileCondition"
108+ | Some ForLoopCondition -> " ForLoopCondition"
109+ | Some AssertCondition -> " AssertCondition"
110+ | Some IfCondition -> " IfCondition"
111+ | Some (Statement _ ) -> " Statement"
112+ | Some (MathOperator _ ) -> " MathOperator"
113+ | Some ArrayValue -> " ArrayValue"
114+ | Some (SetRecordField _ ) -> " SetRecordField"
115+ | Some (RecordField _ ) -> " RecordField"
116+ | Some MaybeUnwrapOption -> " MaybeUnwrapOption"
117+ | Some SwitchReturn -> " SwitchReturn"
118+ | Some TryReturn -> " TryReturn"
119+ | Some StringConcat -> " StringConcat"
120+ | Some (FunctionArgument _ ) -> " FunctionArgument"
121+ | Some ComparisonOperator -> " ComparisonOperator"
122+ | Some IfReturn -> " IfReturn"
123+ | None -> " None"
89124
90125let fprintf = Format. fprintf
91126
@@ -95,33 +130,63 @@ let error_type_text ppf type_clash_context =
95130 | Some (Statement FunctionCall) -> " This function call returns:"
96131 | Some (MathOperator {is_constant = Some _ } ) -> " This value has type:"
97132 | Some ArrayValue -> " This array item has type:"
98- | Some SetRecordField ->
133+ | Some ( SetRecordField _ ) ->
99134 " You're assigning something to this field that has type:"
100135 | _ -> " This has type:"
101136 in
102137 fprintf ppf " %s" text
103138
104139let error_expected_type_text ppf type_clash_context =
105140 match type_clash_context with
106- | Some FunctionArgument ->
107- fprintf ppf " But this function argument is expecting:"
141+ | Some (FunctionArgument {optional; name} ) ->
142+ fprintf ppf " But this%s function argument"
143+ (match optional with
144+ | false -> " "
145+ | true -> " optional" );
146+
147+ (match name with
148+ | Some name -> fprintf ppf " @{<info>~%s@}" name
149+ | None -> () );
150+
151+ fprintf ppf " is expecting:"
108152 | Some ComparisonOperator ->
109153 fprintf ppf " But it's being compared to something of type:"
110- | Some Switch -> fprintf ppf " But this switch is expected to return:"
154+ | Some SwitchReturn -> fprintf ppf " But this switch is expected to return:"
155+ | Some TryReturn -> fprintf ppf " But this try/catch is expected to return:"
156+ | Some WhileCondition ->
157+ fprintf ppf " But a @{<info>while@} loop condition must always be of type:"
158+ | Some ForLoopCondition ->
159+ fprintf ppf " But a @{<info>for@} loop bounds must always be of type:"
111160 | Some IfCondition ->
112161 fprintf ppf " But @{<info>if@} conditions must always be of type:"
162+ | Some AssertCondition -> fprintf ppf " But assertions must always be of type:"
113163 | Some IfReturn ->
114164 fprintf ppf " But this @{<info>if@} statement is expected to return:"
115165 | Some ArrayValue ->
116166 fprintf ppf " But this array is expected to have items of type:"
117- | Some SetRecordField -> fprintf ppf " But this record field is of type:"
167+ | Some (SetRecordField _ ) -> fprintf ppf " But the record field is of type:"
168+ | Some
169+ (RecordField {field_name = " children" ; jsx = Some {jsx_type = `Fragment }})
170+ ->
171+ fprintf ppf " But children of JSX fragments must be of type:"
172+ | Some
173+ (RecordField
174+ {field_name = " children" ; jsx = Some {jsx_type = `CustomComponent }}) ->
175+ fprintf ppf " But children passed to this component must be of type:"
176+ | Some (RecordField {field_name; jsx = Some _ } ) ->
177+ fprintf ppf " But the component prop @{<info>%s@} is expected to have type:"
178+ field_name
179+ | Some (RecordField {field_name} ) ->
180+ fprintf ppf " But the record field @{<info>%s@} is expected to have type:"
181+ field_name
118182 | Some (Statement FunctionCall) -> fprintf ppf " But it's expected to return:"
119183 | Some (MathOperator {operator} ) ->
120184 fprintf ppf
121185 " But it's being used with the @{<info>%s@} operator, which works on:"
122186 operator
123187 | Some StringConcat -> fprintf ppf " But string concatenation is expecting:"
124- | _ -> fprintf ppf " But it's expected to have type:"
188+ | Some MaybeUnwrapOption | None ->
189+ fprintf ppf " But it's expected to have type:"
125190
126191let is_record_type ~extract_concrete_typedecl ~env ty =
127192 try
@@ -201,11 +266,17 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
201266 (if for_float then " int" else " float" )
202267 | _ -> () )
203268 | _ -> () )
204- | Some Switch , _ ->
269+ | Some SwitchReturn , _ ->
205270 fprintf ppf
206271 " \n\n \
207- \ All branches in a @{<info>switch@} must return the same type. To fix \
208- this, change your branch to return the expected type."
272+ \ All branches in a @{<info>switch@} must return the same type.@,\
273+ To fix this, change your branch to return the expected type."
274+ | Some TryReturn , _ ->
275+ fprintf ppf
276+ " \n\n \
277+ \ The @{<info>try@} body and the @{<info>catch@} block must return the \
278+ same type.@,\
279+ To fix this, change your try/catch blocks to return the expected type."
209280 | Some IfCondition , _ ->
210281 fprintf ppf
211282 " \n\n \
@@ -355,6 +426,58 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
355426 single JSX element.@,"
356427 (with_configured_jsx_module " array" )
357428 | _ -> () )
429+ | ( Some (RecordField {optional = true ; field_name; jsx = None }),
430+ Some ({desc = Tconstr (p, _, _)}, _) )
431+ when Path. same Predef. path_option p ->
432+ fprintf ppf
433+ " @,\
434+ @,\
435+ @{<info>%s@} is an optional record field, and you're passing an \
436+ optional value to it.@,\
437+ Values passed to an optional record field don't need to be wrapped in \
438+ an option. You might need to adjust the type of the value supplied.\n \
439+ \ @,\
440+ Possible solutions: @,\
441+ - Unwrap the option from the value you're passing in@,\
442+ - If you really do want to pass the optional value, prepend the value \
443+ with @{<info>?@} to show you want to pass the option, like: \
444+ @{<info>{%s: ?%s@}}"
445+ field_name field_name
446+ (Parser. extract_text_at_loc loc)
447+ | ( Some (RecordField {optional = true ; field_name; jsx = Some _}),
448+ Some ({desc = Tconstr (p, _, _)}, _) )
449+ when Path. same Predef. path_option p ->
450+ fprintf ppf
451+ " @,\
452+ @,\
453+ @{<info>%s@} is an optional component prop, and you're passing an \
454+ optional value to it.@,\
455+ Values passed to an optional component prop don't need to be wrapped in \
456+ an option. You might need to adjust the type of the value supplied.\n \
457+ \ @,\
458+ Possible solutions: @,\
459+ - Unwrap the option from the value you're passing in@,\
460+ - If you really do want to pass the optional value, prepend the value \
461+ with @{<info>?@} to show you want to pass the option, like: \
462+ @{<info>%s=?%s@}"
463+ field_name field_name
464+ (Parser. extract_text_at_loc loc)
465+ | ( Some (FunctionArgument {optional = true }),
466+ Some ({desc = Tconstr (p, _, _)}, _) )
467+ when Path. same Predef. path_option p ->
468+ fprintf ppf
469+ " @,\
470+ @,\
471+ You're passing an optional value into an optional function argument.@,\
472+ Values passed to an optional function argument don't need to be wrapped \
473+ in an option. You might need to adjust the type of the value supplied.\n \
474+ \ @,\
475+ Possible solutions: @,\
476+ - Unwrap the option from the value you're passing in@,\
477+ - If you really do want to pass the optional value, prepend the value \
478+ with @{<info>?@} to show you want to pass the option, like: \
479+ @{<info>?%s@}"
480+ (Parser. extract_text_at_loc loc)
358481 | _ , Some (t1 , t2 ) ->
359482 let is_subtype =
360483 try
@@ -410,9 +533,9 @@ let type_clash_context_from_function sexp sfunct =
410533 Some (MathOperator {for_float = true ; operator; is_constant})
411534 | Pexp_ident {txt = Lident (("/" | "*" | "+" | "-" ) as operator )} ->
412535 Some (MathOperator {for_float = false ; operator; is_constant})
413- | _ -> Some FunctionArgument
536+ | _ -> None
414537
415- let type_clash_context_for_function_argument type_clash_context sarg0 =
538+ let type_clash_context_for_function_argument ~ label type_clash_context sarg0 =
416539 match type_clash_context with
417540 | Some (MathOperator {for_float; operator} ) ->
418541 Some
@@ -427,6 +550,16 @@ let type_clash_context_for_function_argument type_clash_context sarg0 =
427550 Some txt
428551 | _ -> None );
429552 })
553+ | None ->
554+ Some
555+ (FunctionArgument
556+ {
557+ optional = false ;
558+ name =
559+ (match label with
560+ | Asttypes. Nolabel -> None
561+ | Optional {txt = l } | Labelled {txt = l } -> Some l);
562+ })
430563 | type_clash_context -> type_clash_context
431564
432565let type_clash_context_maybe_option ty_expected ty_res =
@@ -468,11 +601,6 @@ let print_contextual_unification_error ppf t1 t2 =
468601 the highlighted pattern in @{<info>Some()@} to make it work.@]"
469602 | _ -> ()
470603
471- type jsx_prop_error_info = {
472- fields : Types .label_declaration list ;
473- props_record_path : Path .t ;
474- }
475-
476604let attributes_include_jsx_component_props (attrs : Parsetree.attributes ) =
477605 attrs
478606 |> List. exists (fun ({Location. txt} , _ ) -> txt = " res.jsxComponentProps" )
@@ -484,18 +612,24 @@ let path_to_jsx_component_name p =
484612
485613let get_jsx_component_props
486614 ~(extract_concrete_typedecl : extract_concrete_typedecl ) env ty p =
487- match Path. last p with
488- | "props" -> (
489- try
490- match extract_concrete_typedecl env ty with
491- | ( _p0,
492- _p,
493- {Types. type_kind = Type_record (fields, _repr); type_attributes} )
494- when attributes_include_jsx_component_props type_attributes ->
495- Some {props_record_path = p; fields}
496- | _ -> None
497- with _ -> None )
498- | _ -> None
615+ match p with
616+ | Path. Pdot (Path. Pident {Ident. name = jsx_module_name}, " fragmentProps" , _)
617+ when Some jsx_module_name = ! configured_jsx_module ->
618+ Some {props_record_path = p; fields = [] ; jsx_type = `Fragment }
619+ | _ -> (
620+ (* TODO: handle lowercase components using JSXDOM.domProps *)
621+ match Path. last p with
622+ | "props" -> (
623+ try
624+ match extract_concrete_typedecl env ty with
625+ | ( _p0,
626+ _p,
627+ {Types. type_kind = Type_record (fields, _repr); type_attributes} )
628+ when attributes_include_jsx_component_props type_attributes ->
629+ Some {props_record_path = p; fields; jsx_type = `CustomComponent }
630+ | _ -> None
631+ with _ -> None )
632+ | _ -> None )
499633
500634let print_component_name ppf (p : Path.t ) =
501635 match path_to_jsx_component_name p with
0 commit comments