@@ -69,23 +69,58 @@ let type_expr ppf typ =
69
69
Printtyp. reset_and_mark_loops typ;
70
70
Printtyp. type_expr ppf typ
71
71
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
+
72
78
type type_clash_statement = FunctionCall
73
79
type 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
+ }
75
87
| ArrayValue
76
88
| MaybeUnwrapOption
77
89
| IfCondition
90
+ | AssertCondition
78
91
| IfReturn
79
- | Switch
92
+ | SwitchReturn
93
+ | TryReturn
80
94
| StringConcat
81
95
| ComparisonOperator
96
+ | WhileCondition
82
97
| MathOperator of {
83
98
for_float : bool ;
84
99
operator : string ;
85
100
is_constant : string option ;
86
101
}
87
- | FunctionArgument
102
+ | FunctionArgument of { optional : bool ; name : string option }
88
103
| 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"
89
124
90
125
let fprintf = Format. fprintf
91
126
@@ -95,33 +130,63 @@ let error_type_text ppf type_clash_context =
95
130
| Some (Statement FunctionCall) -> " This function call returns:"
96
131
| Some (MathOperator {is_constant = Some _ } ) -> " This value has type:"
97
132
| Some ArrayValue -> " This array item has type:"
98
- | Some SetRecordField ->
133
+ | Some ( SetRecordField _ ) ->
99
134
" You're assigning something to this field that has type:"
100
135
| _ -> " This has type:"
101
136
in
102
137
fprintf ppf " %s" text
103
138
104
139
let error_expected_type_text ppf type_clash_context =
105
140
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:"
108
152
| Some ComparisonOperator ->
109
153
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:"
111
160
| Some IfCondition ->
112
161
fprintf ppf " But @{<info>if@} conditions must always be of type:"
162
+ | Some AssertCondition -> fprintf ppf " But assertions must always be of type:"
113
163
| Some IfReturn ->
114
164
fprintf ppf " But this @{<info>if@} statement is expected to return:"
115
165
| Some ArrayValue ->
116
166
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
118
182
| Some (Statement FunctionCall) -> fprintf ppf " But it's expected to return:"
119
183
| Some (MathOperator {operator} ) ->
120
184
fprintf ppf
121
185
" But it's being used with the @{<info>%s@} operator, which works on:"
122
186
operator
123
187
| 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:"
125
190
126
191
let is_record_type ~extract_concrete_typedecl ~env ty =
127
192
try
@@ -201,11 +266,17 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
201
266
(if for_float then " int" else " float" )
202
267
| _ -> () )
203
268
| _ -> () )
204
- | Some Switch , _ ->
269
+ | Some SwitchReturn , _ ->
205
270
fprintf ppf
206
271
" \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."
209
280
| Some IfCondition , _ ->
210
281
fprintf ppf
211
282
" \n\n \
@@ -355,6 +426,58 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
355
426
single JSX element.@,"
356
427
(with_configured_jsx_module " array" )
357
428
| _ -> () )
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)
358
481
| _ , Some (t1 , t2 ) ->
359
482
let is_subtype =
360
483
try
@@ -410,9 +533,9 @@ let type_clash_context_from_function sexp sfunct =
410
533
Some (MathOperator {for_float = true ; operator; is_constant})
411
534
| Pexp_ident {txt = Lident (("/" | "*" | "+" | "-" ) as operator )} ->
412
535
Some (MathOperator {for_float = false ; operator; is_constant})
413
- | _ -> Some FunctionArgument
536
+ | _ -> None
414
537
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 =
416
539
match type_clash_context with
417
540
| Some (MathOperator {for_float; operator} ) ->
418
541
Some
@@ -427,6 +550,16 @@ let type_clash_context_for_function_argument type_clash_context sarg0 =
427
550
Some txt
428
551
| _ -> None );
429
552
})
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
+ })
430
563
| type_clash_context -> type_clash_context
431
564
432
565
let type_clash_context_maybe_option ty_expected ty_res =
@@ -468,11 +601,6 @@ let print_contextual_unification_error ppf t1 t2 =
468
601
the highlighted pattern in @{<info>Some()@} to make it work.@]"
469
602
| _ -> ()
470
603
471
- type jsx_prop_error_info = {
472
- fields : Types .label_declaration list ;
473
- props_record_path : Path .t ;
474
- }
475
-
476
604
let attributes_include_jsx_component_props (attrs : Parsetree.attributes ) =
477
605
attrs
478
606
|> List. exists (fun ({Location. txt} , _ ) -> txt = " res.jsxComponentProps" )
@@ -484,18 +612,24 @@ let path_to_jsx_component_name p =
484
612
485
613
let get_jsx_component_props
486
614
~(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 )
499
633
500
634
let print_component_name ppf (p : Path.t ) =
501
635
match path_to_jsx_component_name p with
0 commit comments