Skip to main content

lisette_diagnostics/
infer.rs

1use crate::LisetteDiagnostic;
2use syntax::ast::{Annotation, BinaryOperator, Span};
3use syntax::types::{SimpleKind, Type};
4
5pub fn blank_import_non_go(blank_span: Span) -> LisetteDiagnostic {
6    LisetteDiagnostic::error("Invalid import")
7        .with_resolve_code("blank_import_non_go")
8        .with_span_label(&blank_span, "only allowed for Go modules")
9        .with_help(
10            "Remove the underscore. Blank imports are allowed only for Go imports, \
11             because Lisette modules have no `init()` side effects.",
12        )
13}
14
15pub fn import_conflict(
16    alias: &str,
17    path1: &str,
18    path2: &str,
19    name_span: Span,
20) -> LisetteDiagnostic {
21    LisetteDiagnostic::error("Import conflict")
22        .with_resolve_code("import_conflict")
23        .with_span_label(
24            &name_span,
25            format!("conflicts with prior import `{}`", alias),
26        )
27        .with_help(format!(
28            "`{}` and `{}` resolve to the same name. Add an alias to at least one of them: \
29             `import my_{} \"{}\"`",
30            path1, path2, alias, path2
31        ))
32}
33
34pub fn reserved_import_alias(alias: &str, alias_span: Span) -> LisetteDiagnostic {
35    LisetteDiagnostic::error("Reserved import alias")
36        .with_resolve_code("reserved_import_alias")
37        .with_span_label(&alias_span, "reserved name")
38        .with_help(format!(
39            "`{}` is a reserved name and cannot be used as an import alias. \
40             Choose a different alias, e.g. `import my_{} \"...\"`",
41            alias, alias
42        ))
43}
44
45pub fn duplicate_import_path(path: &str, name_span: Span) -> LisetteDiagnostic {
46    LisetteDiagnostic::error("Duplicate import")
47        .with_resolve_code("duplicate_import")
48        .with_span_label(&name_span, "already imported above")
49        .with_help(format!(
50            "Module `{}` is already imported. Remove the duplicate import.",
51            path
52        ))
53}
54
55pub fn definition_shadows_import(
56    name: &str,
57    import_path: &str,
58    name_span: Span,
59) -> LisetteDiagnostic {
60    LisetteDiagnostic::error("Definition shadows import")
61        .with_resolve_code("definition_shadows_import")
62        .with_span_label(
63            &name_span,
64            format!("conflicts with imported module `{}`", import_path),
65        )
66        .with_help(format!(
67            "`{}` is already used as a module alias for `{}`. \
68             Rename this definition or use a different import alias.",
69            name, import_path
70        ))
71}
72
73pub fn statement_as_tail(span: Span) -> LisetteDiagnostic {
74    LisetteDiagnostic::error("Statement used as value")
75        .with_infer_code("statement_as_tail")
76        .with_span_label(&span, "this is a statement, not an expression")
77        .with_help(
78            "The last item in this block must be an expression that produces a value. \
79             Statements like `let`, `=`, `task`, and `defer` do not produce values.",
80        )
81}
82
83pub fn invalid_map_initialization(key: &Type, value: &Type, span: Span) -> LisetteDiagnostic {
84    LisetteDiagnostic::error("Invalid `Map` initialization")
85        .with_infer_code("invalid_map_initialization")
86        .with_span_label(&span, "invalid syntax")
87        .with_help(format!(
88            "To initialize a `Map`, use `Map.new<{}, {}>()`",
89            key, value
90        ))
91}
92
93pub fn self_type_not_supported(span: Span) -> LisetteDiagnostic {
94    let name_span = Span::new(span.file_id, span.byte_offset, 4); // "Self" is 4 chars
95    LisetteDiagnostic::error("Use of `Self` type")
96        .with_resolve_code("self_type_not_supported")
97        .with_span_label(&name_span, "invalid type")
98        .with_help(
99            "Use a type parameter instead, e.g. `interface Comparable<T> { fn compare(self, other: T) -> int }`",
100        )
101}
102
103pub fn type_not_found(type_name: &str, annotation_span: Span) -> LisetteDiagnostic {
104    let simple_name = type_name.rsplit('.').next().unwrap_or(type_name);
105    let name_span = Span::new(
106        annotation_span.file_id,
107        annotation_span.byte_offset,
108        simple_name.len() as u32,
109    );
110
111    let looks_like_type_param = simple_name.len() == 1
112        && simple_name.chars().next().is_some_and(|c| c.is_uppercase())
113        || ["Key", "Value", "Item", "Error", "Elem", "In", "Out"].contains(&simple_name);
114
115    if looks_like_type_param {
116        return LisetteDiagnostic::error("Undeclared type parameter")
117            .with_resolve_code("type_not_found")
118            .with_span_label(&name_span, "undeclared")
119            .with_help(format!(
120                "Declare the type parameter, e.g. `impl<{t}>` or `fn foo<{t}>`",
121                t = simple_name
122            ));
123    }
124
125    LisetteDiagnostic::error("Type not found")
126        .with_resolve_code("type_not_found")
127        .with_span_label(&name_span, "type not found in scope")
128        .with_help("Define or import this type")
129}
130
131pub fn undeclared_impl_type_param(
132    type_name: &str,
133    annotation_span: Span,
134    receiver_name: &str,
135) -> LisetteDiagnostic {
136    let name_span = Span::new(
137        annotation_span.file_id,
138        annotation_span.byte_offset,
139        type_name.len() as u32,
140    );
141
142    LisetteDiagnostic::error("Undeclared type parameter")
143        .with_resolve_code("type_not_found")
144        .with_span_label(&name_span, "undeclared")
145        .with_help(format!(
146            "Declare the type parameter: `impl<{t}> {r}<{t}>`",
147            t = type_name,
148            r = receiver_name
149        ))
150}
151
152pub fn type_param_with_args(type_arg_count: usize, span: Span) -> LisetteDiagnostic {
153    let noun = if type_arg_count == 1 {
154        "type argument"
155    } else {
156        "type arguments"
157    };
158
159    LisetteDiagnostic::error("Invalid type argument")
160        .with_infer_code("type_param_with_args")
161        .with_span_label(&span, "type is not parameterized")
162        .with_help(format!("Remove {}", noun))
163}
164
165pub fn type_args_on_non_generic(type_arg_count: usize, span: Span) -> LisetteDiagnostic {
166    let noun = if type_arg_count == 1 {
167        "type argument"
168    } else {
169        "type arguments"
170    };
171
172    LisetteDiagnostic::error("Unexpected type arguments")
173        .with_infer_code("type_arg_on_non_generic")
174        .with_span_label(&span, "accepts no type arguments")
175        .with_help(format!("Remove the {} from this call", noun))
176}
177
178pub fn circular_type_alias(type_name: &str, span: Span) -> LisetteDiagnostic {
179    LisetteDiagnostic::error("Circular type alias")
180        .with_resolve_code("circular_type_alias")
181        .with_span_label(&span, format!("`{}` references itself", type_name))
182        .with_help("Type aliases cannot be recursive")
183}
184
185pub fn const_disallows_composite(span: Span) -> LisetteDiagnostic {
186    LisetteDiagnostic::error("Composite value in `const`")
187        .with_infer_code("const_disallows_composite")
188        .with_span_label(&span, "not allowed")
189        .with_help("`const` only accepts primitive values: `bool`, `int`, `float`, and `string`")
190}
191
192pub fn const_cycle(cycle: &[String], span: Span) -> LisetteDiagnostic {
193    let mut diagnostic = LisetteDiagnostic::error("`const` init cycle")
194        .with_infer_code("const_cycle")
195        .with_help(
196            "`const` initializers cannot refer to themselves, either directly or transitively",
197        );
198    diagnostic = if cycle.len() == 1 {
199        diagnostic.with_span_label(&span, "self-reference")
200    } else {
201        let chain = cycle
202            .iter()
203            .map(|name| format!("`{}`", name))
204            .collect::<Vec<_>>()
205            .join(" → ");
206        diagnostic.with_span_label(&span, format!("cycle: {} → `{}`", chain, cycle[0]))
207    };
208    diagnostic
209}
210
211pub fn name_not_found(
212    variable_name: &str,
213    span: Span,
214    available_names: &[String],
215) -> LisetteDiagnostic {
216    if matches!(variable_name, "nil" | "null" | "Nil" | "undefined") {
217        return LisetteDiagnostic::error(format!("`{}` is not supported", variable_name))
218            .with_resolve_code("nil_not_supported")
219            .with_span_label(&span, "does not exist")
220            .with_help("Absence is encoded with `Option<T>` in Lisette. Use `None` to represent absent values.");
221    }
222
223    if let Some(hint) = go_builtin_hint(variable_name) {
224        return LisetteDiagnostic::error("Name not found")
225            .with_resolve_code("name_not_found")
226            .with_span_label(&span, "name not found in scope")
227            .with_help(hint);
228    }
229
230    let mut diagnostic = LisetteDiagnostic::error("Name not found")
231        .with_resolve_code("name_not_found")
232        .with_span_label(&span, "name not found in scope");
233
234    let suggestion = available_names
235        .iter()
236        .filter_map(|c| {
237            let d = levenshtein_distance(variable_name, c);
238            (d <= 2).then_some((c, d))
239        })
240        .min_by_key(|(_, d)| *d)
241        .map(|(c, _)| c.clone());
242
243    if let Some(suggestion) = suggestion {
244        diagnostic = diagnostic.with_help(format!("Did you mean `{}`?", suggestion));
245    } else {
246        diagnostic = diagnostic.with_help(format!("Define or import `{}`.", variable_name));
247    }
248
249    diagnostic
250}
251
252pub fn self_in_static_method(span: Span) -> LisetteDiagnostic {
253    LisetteDiagnostic::error("Invalid `self`")
254        .with_resolve_code("self_in_static_method")
255        .with_span_label(&span, "`self` is not available here")
256        .with_help("Add a `self` parameter to the method if you need an instance method")
257}
258
259pub fn static_method_called_on_instance(
260    method_name: &str,
261    type_name: &str,
262    span: Span,
263) -> LisetteDiagnostic {
264    LisetteDiagnostic::error("Static method called on instance")
265        .with_infer_code("static_method_on_instance")
266        .with_span_label(&span, format!("`{}` is a static method", method_name))
267        .with_help(format!(
268            "Call it as `{}.{}(...)` on the type, not on an instance",
269            type_name, method_name
270        ))
271}
272
273pub fn function_or_value_not_found_in_module(name: &str, span: Span) -> LisetteDiagnostic {
274    LisetteDiagnostic::error("Name not found")
275        .with_resolve_code("not_found_in_module")
276        .with_span_label(&span, format!("`{}` not found in module", name))
277        .with_help("Ensure the name is exported and spelled correctly")
278}
279
280pub fn receiver_type_mismatch(
281    impl_type: &str,
282    receiver_type: &str,
283    span: Span,
284) -> LisetteDiagnostic {
285    LisetteDiagnostic::error("Type mismatch")
286        .with_infer_code("receiver_type_mismatch")
287        .with_span_label(
288            &span,
289            format!(
290                "expected `{}` or `Ref<{}>`, found `{}`",
291                impl_type, impl_type, receiver_type
292            ),
293        )
294        .with_help(format!(
295            "Change the receiver type to `{}` or `Ref<{}>`",
296            impl_type, impl_type
297        ))
298}
299
300pub fn receiver_must_be_named_self(actual_name: &str, span: Span) -> LisetteDiagnostic {
301    LisetteDiagnostic::error("Invalid receiver name")
302        .with_infer_code("receiver_not_self")
303        .with_span_label(&span, "expected `self`")
304        .with_help(format!(
305            "Rename `{}` to `self`. In an instance method definition, Lisette expects the first parameter to be named `self`",
306            actual_name
307        ))
308}
309
310pub fn stringer_signature_mismatch(method_name: &str, span: Span) -> LisetteDiagnostic {
311    LisetteDiagnostic::error("Reserved method signature")
312        .with_infer_code("stringer_signature_mismatch")
313        .with_span_label(
314            &span,
315            format!("`{}` must have signature `(self) -> string`", method_name),
316        )
317        .with_help(format!(
318            "`{}` is reserved for the Go `fmt.Stringer` (or `fmt.GoStringer`) interface and is auto-emitted by Lisette. Either change the signature to `(self) -> string`, or rename the method",
319            method_name
320        ))
321}
322
323pub fn disallowed_mutation(
324    variable_name: &str,
325    span: Span,
326    self_type_name: Option<&str>,
327    is_match_arm_binding: bool,
328    is_const_binding: bool,
329) -> LisetteDiagnostic {
330    if variable_name == "self" {
331        if let Some(type_name) = self_type_name {
332            LisetteDiagnostic::error("Immutable receiver")
333                .with_infer_code("value_receiver_immutable")
334                .with_span_label(&span, "receiver is immutable")
335                .with_help(format!(
336                    "Use `self: Ref<{type_name}>` to make the receiver mutable"
337                ))
338        } else {
339            LisetteDiagnostic::error("Immutable receiver")
340                .with_infer_code("value_receiver_immutable")
341                .with_span_label(&span, "receiver is immutable")
342                .with_help("Use `self: Ref<Self>` to make the receiver mutable")
343        }
344    } else if is_const_binding {
345        LisetteDiagnostic::error("Cannot mutate `const`")
346            .with_infer_code("immutable")
347            .with_span_label(&span, "cannot mutate a `const`")
348            .with_help(format!(
349                "`const` bindings are immutable. Rebind with `let mut {variable_name} = {variable_name}` to mutate a local copy"
350            ))
351    } else if is_match_arm_binding {
352        LisetteDiagnostic::error("Immutable variable")
353            .with_infer_code("immutable")
354            .with_span_label(&span, "cannot mutate an immutable variable")
355            .with_help(format!(
356                "Pattern bindings are immutable; rebind with `let mut {variable_name} = {variable_name}` to mutate"
357            ))
358    } else {
359        LisetteDiagnostic::error("Immutable variable")
360            .with_infer_code("immutable")
361            .with_span_label(&span, "cannot mutate an immutable variable")
362            .with_help(format!(
363                "Declare using `let mut {variable_name}` to make the variable mutable"
364            ))
365    }
366}
367
368pub fn self_reference_in_assignment(span: Span) -> LisetteDiagnostic {
369    LisetteDiagnostic::error("Cannot reassign variable while taking its reference")
370        .with_infer_code("self_reference_in_assignment")
371        .with_span_label(&span, "disallowed")
372        .with_help("Separate the reassignment from reference taking, or use a different variable")
373}
374
375pub fn uppercase_binding(span: Span, name: &str) -> LisetteDiagnostic {
376    LisetteDiagnostic::error("Invalid binding name")
377        .with_infer_code("uppercase_binding")
378        .with_span_label(&span, "binding names must start with a lowercase letter")
379        .with_help(format!("Use a lowercase name instead of `{}`", name))
380}
381
382pub fn enum_variant_constructor_not_found(
383    span: Span,
384    enum_info: Option<(&str, &[String])>,
385    variant_name: &str,
386) -> LisetteDiagnostic {
387    let help = if let Some((enum_name, variants)) = enum_info {
388        if variants.iter().any(|v| v == variant_name) {
389            format!("Use `{}.{}` to match this variant", enum_name, variant_name)
390        } else if let Some(closest) = variants
391            .iter()
392            .filter_map(|v| {
393                let d = levenshtein_distance(variant_name, v);
394                (d <= 2).then_some((v, d))
395            })
396            .min_by_key(|(_, d)| *d)
397            .map(|(v, _)| v)
398        {
399            format!("Did you mean `{}.{}`?", enum_name, closest)
400        } else {
401            let variants_fmt = format_list(variants, |v| format!("`{}.{}`", enum_name, v));
402            format!(
403                "Available variants for `{}` are {}",
404                enum_name, variants_fmt
405            )
406        }
407    } else {
408        "Check that the variant is defined in the enum and spelled correctly".to_string()
409    };
410
411    LisetteDiagnostic::error("Variant not found")
412        .with_resolve_code("variant_not_found")
413        .with_span_label(&span, "not found")
414        .with_help(help)
415}
416
417pub fn value_enum_in_source_file(enum_name: &str, span: Span) -> LisetteDiagnostic {
418    LisetteDiagnostic::error("Invalid value enum")
419        .with_infer_code("value_enum_outside_typedef")
420        .with_span_label(&span, "not allowed in .lis files")
421        .with_help(format!(
422            "Use a regular enum instead: `enum {} {{ A, B, C }}`. Value enums exist only to represent Go's enums in typedefs.",
423            enum_name
424        ))
425}
426
427pub fn arity_mismatch(
428    expected: &[Type],
429    actual: &[Type],
430    generic_params: &[String],
431    is_constructor: bool,
432    span: Span,
433) -> LisetteDiagnostic {
434    let expected_str = if !generic_params.is_empty() {
435        generic_params.join(", ")
436    } else {
437        expected
438            .iter()
439            .map(|t| t.to_string())
440            .collect::<Vec<_>>()
441            .join(", ")
442    };
443
444    let actual_str = actual
445        .iter()
446        .map(|t| t.to_string())
447        .collect::<Vec<_>>()
448        .join(", ");
449
450    let expected_count = expected.len();
451    let actual_count = actual.len();
452    let expected_word = if expected_count == 1 {
453        "argument"
454    } else {
455        "arguments"
456    };
457
458    LisetteDiagnostic::error("Wrong argument count")
459        .with_infer_code("arg_count_mismatch")
460        .with_span_label(
461            &span,
462            format!("expected `({})`, found `({})`", expected_str, actual_str),
463        )
464        .with_help(format!(
465            "This {} expects {} {} but received {} arguments",
466            if is_constructor {
467                "constructor"
468            } else {
469                "function"
470            },
471            expected_count,
472            expected_word,
473            actual_count
474        ))
475}
476
477pub fn generics_arity_mismatch(
478    expected_generic_params: &[String],
479    actual_type_args: &[Annotation],
480    actual_types: &[Type],
481    span: Span,
482) -> LisetteDiagnostic {
483    let expected: Vec<Type> = expected_generic_params
484        .iter()
485        .map(|param| Type::Parameter(param.as_str().into()))
486        .collect();
487
488    let expected_str = expected
489        .iter()
490        .map(|t| t.to_string())
491        .collect::<Vec<_>>()
492        .join(", ");
493
494    let actual_str = actual_types
495        .iter()
496        .map(|t| t.to_string())
497        .collect::<Vec<_>>()
498        .join(", ");
499
500    let expected_count = expected.len();
501    let actual_count = actual_types.len();
502    let expected_word = if expected_count == 1 {
503        "type parameter"
504    } else {
505        "type parameters"
506    };
507
508    let generics_span =
509        if let (Some(first), Some(last)) = (actual_type_args.first(), actual_type_args.last()) {
510            let first_span = first.get_span();
511            let last_span = last.get_span();
512            Span::new(
513                first_span.file_id,
514                first_span.byte_offset.saturating_sub(1),
515                (last_span.byte_offset + last_span.byte_length + 1)
516                    .saturating_sub(first_span.byte_offset.saturating_sub(1)),
517            )
518        } else {
519            span
520        };
521
522    LisetteDiagnostic::error("Wrong type argument count")
523        .with_infer_code("type_arg_count_mismatch")
524        .with_span_label(
525            &generics_span,
526            format!("expected `<{}>`, found `<{}>`", expected_str, actual_str),
527        )
528        .with_help(format!(
529            "This type expects {} {} but received {} type parameters",
530            expected_count, expected_word, actual_count
531        ))
532}
533
534pub fn tuple_arity_mismatch(
535    pattern_arity: usize,
536    expected_arity: usize,
537    span: Span,
538) -> LisetteDiagnostic {
539    LisetteDiagnostic::error("Tuple arity mismatch")
540        .with_infer_code("tuple_element_count_mismatch")
541        .with_span_label(
542            &span,
543            format!(
544                "expected {} elements, found {} elements",
545                expected_arity, pattern_arity
546            ),
547        )
548        .with_help("Adjust the pattern to match the number of elements in the tuple.")
549}
550
551pub fn struct_not_found(identifier: &str, span: Span) -> LisetteDiagnostic {
552    let simple_name = identifier.rsplit('.').next().unwrap_or(identifier);
553    let name_span = Span::new(span.file_id, span.byte_offset, simple_name.len() as u32);
554
555    LisetteDiagnostic::error("Struct not found")
556        .with_resolve_code("struct_not_found")
557        .with_span_label(&name_span, "struct not found in scope")
558        .with_help("Define or import this struct")
559}
560
561pub fn struct_missing_fields(
562    struct_name: &str,
563    missing: &[String],
564    span: Span,
565) -> LisetteDiagnostic {
566    let fields_list = missing.join(", ");
567
568    let simple_name = struct_name.rsplit('.').next().unwrap_or(struct_name);
569    let name_span = Span::new(span.file_id, span.byte_offset, simple_name.len() as u32);
570
571    LisetteDiagnostic::error(format!("Struct `{}` is missing fields", simple_name))
572        .with_infer_code("missing_struct_fields")
573        .with_span_label(&name_span, format!("missing fields: {}", fields_list))
574        .with_help("Initialize all fields in this struct literal")
575}
576
577pub fn pattern_missing_fields(missing: &[String], span: Span) -> LisetteDiagnostic {
578    let (noun, fields_fmt) = if missing.len() == 1 {
579        ("field", format!("`{}`", missing[0]))
580    } else {
581        let formatted: Vec<String> = missing.iter().map(|f| format!("`{}`", f)).collect();
582        ("fields", formatted.join(", "))
583    };
584
585    let pronoun = if missing.len() == 1 { "it" } else { "them" };
586
587    LisetteDiagnostic::error("Missing pattern fields")
588        .with_infer_code("pattern_missing_fields")
589        .with_span_label(&span, format!("missing {}", fields_fmt))
590        .with_help(format!(
591            "Include the missing {}, or use `..` to ignore {}",
592            noun, pronoun
593        ))
594}
595
596pub fn private_field_access(field_name: &str, struct_name: &str, span: Span) -> LisetteDiagnostic {
597    LisetteDiagnostic::error("Private field")
598        .with_resolve_code("private_field_spread")
599        .with_span_label(&span, "private")
600        .with_help(format!(
601            "Cannot access private field `{}` of struct `{}`. Mark the field as `pub`.",
602            field_name, struct_name
603        ))
604}
605
606pub fn private_method_access(method_name: &str, type_name: &str, span: Span) -> LisetteDiagnostic {
607    LisetteDiagnostic::error("Private method")
608        .with_span_label(&span, "private")
609        .with_help(format!(
610            "Cannot access private method `{}` of type `{}`. Mark the method as `pub`.",
611            method_name, type_name
612        ))
613}
614
615pub fn private_field_in_spread(
616    field_name: &str,
617    struct_name: &str,
618    span: Span,
619) -> LisetteDiagnostic {
620    LisetteDiagnostic::error("Private field")
621        .with_resolve_code("private_field_spread")
622        .with_span_label(&span, "private")
623        .with_help(format!(
624            "Cannot spread `{}` because field `{}` is private. Mark the field as `pub`.",
625            struct_name, field_name
626        ))
627}
628
629pub fn member_not_found(
630    ty: &Type,
631    field: &str,
632    span: Span,
633    available_fields: Option<&[String]>,
634) -> LisetteDiagnostic {
635    let mut diagnostic = LisetteDiagnostic::error("Member not found")
636        .with_infer_code("member_not_found")
637        .with_span_label(&span, format!("no member `{}` on type `{}`", field, ty));
638
639    let suggestion = available_fields.and_then(|fields| find_similar_name(field, fields));
640
641    if let Some(suggestion) = suggestion {
642        diagnostic = diagnostic.with_help(format!("Did you mean `{}`?", suggestion));
643    } else {
644        diagnostic = diagnostic.with_help("Ensure the field or method is defined on this type");
645    }
646
647    diagnostic
648}
649
650pub fn not_numeric(ty: &Type, span: Span) -> LisetteDiagnostic {
651    LisetteDiagnostic::error("Type mismatch")
652        .with_infer_code("type_mismatch")
653        .with_span_label(&span, format!("expected `int` or `float`, found `{}`", ty))
654        .with_help("The negation operator `-` can only be used with `int` or `float`")
655}
656
657pub fn not_numeric_for_binary(
658    operator: &BinaryOperator,
659    ty: &Type,
660    span: Span,
661) -> LisetteDiagnostic {
662    LisetteDiagnostic::error("Type mismatch")
663        .with_infer_code("type_mismatch")
664        .with_span_label(&span, format!("expected `int` or `float`, found `{}`", ty))
665        .with_help(format!(
666            "The `{}` operator can only be used with `int` or `float`",
667            operator
668        ))
669}
670
671pub fn binary_operator_type_mismatch(
672    operator: &BinaryOperator,
673    left_ty: &Type,
674    right_ty: &Type,
675    span: Span,
676) -> LisetteDiagnostic {
677    let label_msg = format!(
678        "cannot {} `{}` and `{}`",
679        operator_verb(operator),
680        left_ty,
681        right_ty
682    );
683
684    LisetteDiagnostic::error("Type mismatch")
685        .with_infer_code("type_mismatch")
686        .with_span_label(&span, label_msg)
687        .with_help(format!(
688            "The `{}` operator {}",
689            operator,
690            operator_help(operator)
691        ))
692}
693
694pub fn not_orderable(ty: &Type, span: Span) -> LisetteDiagnostic {
695    LisetteDiagnostic::error("Type mismatch")
696        .with_infer_code("type_mismatch")
697        .with_span_label(&span, format!("expected comparable, found `{}`", ty))
698        .with_help("Use comparison operators only with numeric, string, or boolean types")
699}
700
701pub fn not_comparable(ty: &Type, reason: &str, span: Span) -> LisetteDiagnostic {
702    LisetteDiagnostic::error("Type mismatch")
703        .with_infer_code("type_mismatch")
704        .with_span_label(&span, format!("`{}` cannot be compared with `==`", ty))
705        .with_help(format!(
706            "The `==` and `!=` operators cannot be used on {} because they are not comparable in Go",
707            reason
708        ))
709}
710
711pub fn not_orderable_bound(span: Span) -> LisetteDiagnostic {
712    LisetteDiagnostic::error("Bound not satisfied")
713        .with_infer_code("not_orderable_bound")
714        .with_span_label(&span, "does not satisfy `cmp.Ordered`")
715        .with_help(
716            "The type parameter must be `cmp.Ordered` but the argument is not orderable. \
717             Relax the bound or pass an argument that satisfies it",
718        )
719}
720
721pub fn not_comparable_bound(span: Span) -> LisetteDiagnostic {
722    LisetteDiagnostic::error("Bound not satisfied")
723        .with_infer_code("not_comparable_bound")
724        .with_span_label(&span, "does not satisfy `Comparable`")
725        .with_help(
726            "The parameter must be `Comparable` but the argument is not comparable. \
727             Relax the bound or pass an argument that satisfies it",
728        )
729}
730
731pub fn bound_only_in_value_position(name: &str, span: Span) -> LisetteDiagnostic {
732    LisetteDiagnostic::error(format!("`{}` is a bound, not a value type", name))
733        .with_infer_code("bound_only_in_value_position")
734        .with_span_label(&span, "not allowed here")
735        .with_help(format!(
736            "Use `{}` only as a bound to constrain a generic parameter, e.g. `fn f<T: {}>(x: T)`",
737            name, name
738        ))
739}
740
741pub fn missing_bound_on_param(
742    param_name: &str,
743    required_bound: &str,
744    span: Span,
745) -> LisetteDiagnostic {
746    let short = required_bound.rsplit('.').next().unwrap_or(required_bound);
747    LisetteDiagnostic::error("Missing bound on type parameter")
748        .with_infer_code("missing_bound_on_param")
749        .with_span_label(&span, format!("does not satisfy `{}`", short))
750        .with_help(format!(
751            "The parameter must be `{}` but the argument is unbounded. \
752             Add this bound to the enclosing function: `<{}: {}>`",
753            required_bound, param_name, required_bound
754        ))
755}
756
757pub fn division_by_zero(span: Span) -> LisetteDiagnostic {
758    LisetteDiagnostic::error("Division by zero")
759        .with_infer_code("division_by_zero")
760        .with_span_label(&span, "cannot divide by zero")
761        .with_help("This operation will panic at runtime")
762}
763
764pub fn incompatible_named_numeric_types(underlying_ty: &Type, span: Span) -> LisetteDiagnostic {
765    LisetteDiagnostic::error("Type mismatch")
766        .with_infer_code("incompatible_named_numeric_types")
767        .with_span_label(&span, "cannot compute")
768        .with_help(format!(
769            "Cast one to the other's type, or convert both to `{}`",
770            underlying_ty
771        ))
772}
773
774pub fn invalid_division_order(
775    operator: &BinaryOperator,
776    left_ty: &Type,
777    right_ty: &Type,
778    span: Span,
779) -> LisetteDiagnostic {
780    let (op_symbol, help_msg) = match operator {
781        BinaryOperator::Division => (
782            "/",
783            format!(
784                "To divide by `{}`, the dividend (left operand) must also be `{}`",
785                right_ty, right_ty
786            ),
787        ),
788        BinaryOperator::Remainder => (
789            "%",
790            format!(
791                "To take the remainder by `{}`, the dividend (left operand) must also be `{}`",
792                right_ty, right_ty
793            ),
794        ),
795        _ => unreachable!(),
796    };
797
798    LisetteDiagnostic::error("Invalid operation")
799        .with_infer_code("invalid_division_order")
800        .with_span_label(
801            &span,
802            format!("cannot compute `{}` {} `{}`", left_ty, op_symbol, right_ty),
803        )
804        .with_help(help_msg)
805}
806
807pub fn branch_type_mismatch(
808    consequence_ty: &Type,
809    consequence_span: Span,
810    alternative_ty: &Type,
811    alternative_span: Span,
812) -> LisetteDiagnostic {
813    LisetteDiagnostic::error("Type mismatch")
814        .with_infer_code("type_mismatch")
815        .with_span_label(
816            &consequence_span,
817            format!("this branch returns `{}`", consequence_ty),
818        )
819        .with_span_label(
820            &alternative_span,
821            format!("this branch returns `{}`", alternative_ty),
822        )
823        .with_help("All branches must return the same type")
824}
825
826pub fn missing_else_branch(span: Span) -> LisetteDiagnostic {
827    LisetteDiagnostic::error("Missing `else` branch")
828        .with_infer_code("missing_else_branch")
829        .with_span_label(&span, "`else` branch required")
830        .with_help("Add an `else` branch")
831}
832
833pub fn let_else_must_diverge(span: Span) -> LisetteDiagnostic {
834    LisetteDiagnostic::error("Invalid `else` block")
835        .with_infer_code("let_else_must_diverge")
836        .with_span_primary_label(&span, "this branch does not diverge")
837        .with_help("Add `return`, `break`, `continue`, or a diverging call in the `else` block")
838}
839
840pub fn return_outside_function(span: Span) -> LisetteDiagnostic {
841    LisetteDiagnostic::error("`return` outside function")
842        .with_infer_code("return_outside_function")
843        .with_span_label(&span, "`return` outside function")
844        .with_help("Use `return` only inside a function body")
845}
846
847pub fn disallowed_mut_use(span: Span) -> LisetteDiagnostic {
848    LisetteDiagnostic::error("Invalid `mut`")
849        .with_infer_code("mut_not_allowed")
850        .with_span_label(&span, "not allowed here")
851        .with_help("`mut` is not allowed with destructuring patterns")
852}
853
854pub fn cannot_match_on_functions(span: Span) -> LisetteDiagnostic {
855    LisetteDiagnostic::error("Invalid pattern")
856        .with_infer_code("invalid_pattern")
857        .with_span_label(&span, "cannot pattern match on functions")
858        .with_help("Functions cannot be compared for equality")
859}
860
861pub fn cannot_match_on_unknown(span: Span) -> LisetteDiagnostic {
862    LisetteDiagnostic::error("Cannot match on Unknown")
863        .with_infer_code("cannot_match_on_unknown")
864        .with_span_label(&span, "is type `Unknown`")
865        .with_help("Use `assert_type` to narrow this value into a concrete type before matching. Example: `let value = assert_type<MyType>(x)?`")
866}
867
868pub fn cannot_match_on_unconstrained_type(span: Span) -> LisetteDiagnostic {
869    LisetteDiagnostic::error("Uninferred type")
870        .with_infer_code("cannot_match_on_unconstrained_type")
871        .with_span_label(&span, "type cannot be inferred at this point")
872        .with_help("Add a type annotation on the value before matching on it")
873}
874
875pub fn duplicate_binding_in_pattern(
876    name: &str,
877    first_span: Span,
878    second_span: Span,
879) -> LisetteDiagnostic {
880    LisetteDiagnostic::error("Duplicate binding")
881        .with_infer_code("duplicate_binding_in_pattern")
882        .with_span_label(&first_span, format!("first use of `{}`", name))
883        .with_span_label(&second_span, "used again")
884        .with_help("Remove the duplicate binding")
885}
886
887pub fn literal_pattern_in_binding(span: Span) -> LisetteDiagnostic {
888    LisetteDiagnostic::error("Pattern might not match")
889        .with_infer_code("literal_in_binding")
890        .with_span_label(&span, "value might not equal this literal")
891        .with_help("Use `match` or `if` to compare values")
892}
893
894pub fn as_binding_in_irrefutable_context(span: Span) -> LisetteDiagnostic {
895    LisetteDiagnostic::error("Invalid `as` binding")
896        .with_infer_code("as_binding_in_irrefutable_context")
897        .with_span_label(&span, "`as` is disallowed here")
898        .with_help("Use `as` only in `match`, `if let`, and `while let`")
899}
900
901pub fn select_some_as_binding_not_supported(span: Span) -> LisetteDiagnostic {
902    LisetteDiagnostic::error("Cannot alias `Some(...)` in select")
903        .with_infer_code("select_some_as_not_supported")
904        .with_span_label(&span, "`as` cannot be placed around `Some(...)`")
905        .with_help(
906            "Place `as` inside `Some(...)` to bind the received value: `Some(value as alias)`",
907        )
908}
909
910pub fn redundant_as_identifier(inner: &str, alias: &str, span: Span) -> LisetteDiagnostic {
911    LisetteDiagnostic::error("Redundant `as` binding")
912        .with_infer_code("redundant_as_binding")
913        .with_span_label(&span, format!("`{}` already binds this value", inner))
914        .with_help(format!(
915            "Use `{}` directly, or rename `{}` to `{}`",
916            alias, inner, alias
917        ))
918}
919
920pub fn redundant_as_wildcard(alias: &str, span: Span) -> LisetteDiagnostic {
921    LisetteDiagnostic::error("Redundant `as` binding")
922        .with_infer_code("redundant_as_binding")
923        .with_span_label(&span, "`_` binds nothing")
924        .with_help(format!("Replace `_ as {}` with just `{}`", alias, alias))
925}
926
927pub fn redundant_as_literal(literal: &str, alias: &str, span: Span) -> LisetteDiagnostic {
928    LisetteDiagnostic::error("Redundant `as` binding")
929        .with_infer_code("redundant_as_binding")
930        .with_span_label(&span, format!("`{}` is always `{}`", alias, literal))
931        .with_help(format!(
932            "Replace `{} as {}` with just `{}`",
933            literal, alias, literal
934        ))
935}
936
937pub fn or_pattern_in_irrefutable_context(span: Span) -> LisetteDiagnostic {
938    LisetteDiagnostic::error("Invalid or-pattern")
939        .with_infer_code("or_pattern_in_irrefutable")
940        .with_span_label(&span, "or-patterns are not allowed here")
941        .with_help("Use a `match` expression instead.")
942        .with_note("Or-patterns can only be used in `match`, `if let`, and `while let`.")
943}
944
945pub fn or_pattern_binding_mismatch(
946    span: Span,
947    missing_in_later: &[&str],
948    missing_in_first: &[&str],
949) -> LisetteDiagnostic {
950    let missing = if !missing_in_later.is_empty() {
951        missing_in_later.join(", ")
952    } else {
953        missing_in_first.join(", ")
954    };
955
956    LisetteDiagnostic::error("Invalid or-pattern")
957        .with_infer_code("or_pattern_binding_mismatch")
958        .with_span_label(&span, "only bound here")
959        .with_help(format!(
960            "Variable {} is not bound in all alternatives. Use a wildcard `_` instead of a binding, or ensure all alternatives bind the same variable",
961            missing
962        ))
963}
964
965pub fn or_pattern_type_mismatch(span: Span, first_ty: &str, alt_ty: &str) -> LisetteDiagnostic {
966    LisetteDiagnostic::error("Invalid or-pattern")
967        .with_infer_code("or_pattern_type_mismatch")
968        .with_span_label(
969            &span,
970            format!("expected `{}`, found `{}`", first_ty, alt_ty),
971        )
972        .with_help(
973            "Use a wildcard `_` instead of a binding, or use separate match arms for each variant",
974        )
975}
976
977pub fn unknown_iterable_type(span: Span) -> LisetteDiagnostic {
978    LisetteDiagnostic::error("Uninferrable type")
979        .with_infer_code("type_not_inferred")
980        .with_span_label(&span, "cannot be inferred")
981        .with_help("Add a type annotation to the iterable expression")
982}
983
984pub fn not_iterable(ty: &Type, span: Span) -> LisetteDiagnostic {
985    LisetteDiagnostic::error("Not iterable")
986        .with_infer_code("not_iterable")
987        .with_span_label(&span, format!("`{}` is not iterable", ty))
988        .with_help("Use `Slice`, `Map`, `Range`, or `string`")
989}
990
991pub fn tuple_literal_required_in_loop(span: Span) -> LisetteDiagnostic {
992    LisetteDiagnostic::error("Invalid loop pattern")
993        .with_infer_code("invalid_pattern")
994        .with_span_label(&span, "tuple literal required here")
995        .with_help("Use `(key, value)` destructuring pattern for map or enumerated iteration")
996}
997
998pub fn propagate_on_partial(span: Span) -> LisetteDiagnostic {
999    LisetteDiagnostic::error("Cannot use `?` on `Partial`")
1000        .with_infer_code("propagate_on_partial")
1001        .with_span_label(&span, "`Partial` requires explicit `match`")
1002        .with_help(
1003            "The `?` operator is incompatible with `Partial` because it has \
1004             three variants. Use `match` to handle `Ok`, `Err`, and `Both` \
1005             explicitly.",
1006        )
1007}
1008
1009pub fn try_requires_result_or_option(span: Span) -> LisetteDiagnostic {
1010    LisetteDiagnostic::error("Type mismatch")
1011        .with_infer_code("try_requires_result_or_option")
1012        .with_span_label(&span, "expects `Result` or `Option`")
1013        .with_help("Use the `?` operator only on `Result` or `Option`")
1014}
1015
1016pub fn try_outside_function(span: Span) -> LisetteDiagnostic {
1017    LisetteDiagnostic::error("`?` outside function")
1018        .with_infer_code("try_outside_function")
1019        .with_span_label(&span, "`?` outside function")
1020        .with_help("Use `?` only inside a function that returns `Result` or `Option`")
1021}
1022
1023pub fn try_return_type_mismatch(expected: &str, actual_ty: &Type, span: Span) -> LisetteDiagnostic {
1024    LisetteDiagnostic::error("Type mismatch")
1025        .with_infer_code("try_return_type_mismatch")
1026        .with_span_label(
1027            &span,
1028            format!(
1029                "expects `{}`, but function returns `{}`",
1030                expected, actual_ty
1031            ),
1032        )
1033        .with_help(format!(
1034            "Change the function return type to `{}` or remove the `?` operator",
1035            expected
1036        ))
1037}
1038
1039pub fn try_block_empty(span: Span) -> LisetteDiagnostic {
1040    LisetteDiagnostic::error("Empty `try` block")
1041        .with_infer_code("try_block_empty")
1042        .with_span_label(&span, "empty")
1043        .with_help("Ensure the `try` block contains at least one expression")
1044}
1045
1046pub fn try_block_no_question_mark(try_keyword_span: Span) -> LisetteDiagnostic {
1047    LisetteDiagnostic::error("Useless `try` block")
1048        .with_infer_code("try_block_no_question_mark")
1049        .with_span_label(&try_keyword_span, "no `?` operator found")
1050        .with_help("A `try` block must contain at least one `?` for propagation")
1051}
1052
1053pub fn mixed_carriers_in_try_block(span: Span) -> LisetteDiagnostic {
1054    LisetteDiagnostic::error("Mixed `try` block")
1055        .with_infer_code("try_block_mixed_carriers")
1056        .with_span_label(&span, "mixing `Option` and `Result`")
1057        .with_help(
1058            "A `try` block must use either all `Option` operations or all `Result` operations",
1059        )
1060}
1061
1062pub fn break_outside_loop(span: Span) -> LisetteDiagnostic {
1063    LisetteDiagnostic::error("`break` outside loop")
1064        .with_infer_code("break_outside_loop")
1065        .with_span_label(&span, "not inside a loop")
1066        .with_help("`break` can only be used inside `loop`, `for`, or `while`")
1067}
1068
1069pub fn continue_outside_loop(span: Span) -> LisetteDiagnostic {
1070    LisetteDiagnostic::error("`continue` outside loop")
1071        .with_infer_code("continue_outside_loop")
1072        .with_span_label(&span, "not inside a loop")
1073        .with_help("`continue` can only be used inside `loop`, `for`, or `while`")
1074}
1075
1076pub fn nested_function(span: Span) -> LisetteDiagnostic {
1077    LisetteDiagnostic::error("Nested function declaration")
1078        .with_infer_code("nested_function")
1079        .with_span_label(&span, "functions can only be declared at top level")
1080        .with_help("Use a lambda instead: `|x| x + 1` or `|x| { ... }`")
1081}
1082
1083pub fn return_in_try_block(span: Span) -> LisetteDiagnostic {
1084    LisetteDiagnostic::error("`return` in `try` block")
1085        .with_infer_code("try_block_return")
1086        .with_span_label(&span, "not inside a function")
1087        .with_help(
1088            "Use `return` inside a function, or use `Err(...)? ` to exit the `try` block early",
1089        )
1090}
1091
1092pub fn break_in_try_block(span: Span) -> LisetteDiagnostic {
1093    LisetteDiagnostic::error("`break` in `try` block")
1094        .with_infer_code("try_block_break")
1095        .with_span_label(&span, "not inside a loop")
1096        .with_help("Use `break` inside a loop, or use `Err(...)? ` to exit the `try` block early")
1097}
1098
1099pub fn continue_in_try_block(span: Span) -> LisetteDiagnostic {
1100    LisetteDiagnostic::error("`continue` in `try` block")
1101        .with_infer_code("try_block_continue")
1102        .with_span_label(&span, "not inside a loop")
1103        .with_help(
1104            "Use `continue` inside a loop, or use `Err(...)? ` to exit the `try` block early",
1105        )
1106}
1107
1108pub fn recover_block_empty(span: Span) -> LisetteDiagnostic {
1109    LisetteDiagnostic::warn("Empty `recover` block")
1110        .with_infer_code("recover_block_empty")
1111        .with_span_label(&span, "empty")
1112        .with_help("Ensure the `recover` block contains at least one expression that may panic")
1113}
1114
1115pub fn recover_cannot_use_question_mark(span: Span) -> LisetteDiagnostic {
1116    LisetteDiagnostic::error("`?` in `recover` block")
1117        .with_infer_code("recover_cannot_use_question_mark")
1118        .with_span_label(&span, "cannot propagate to `recover` block")
1119        .with_help(
1120            "Use a `try` block inside the `recover` block, or handle the `Result` explicitly",
1121        )
1122}
1123
1124pub fn return_in_recover_block(span: Span) -> LisetteDiagnostic {
1125    LisetteDiagnostic::error("`return` in `recover` block")
1126        .with_infer_code("recover_block_return")
1127        .with_span_label(&span, "not allowed inside `recover` block")
1128        .with_help("Remove the `return`, or move it inside a nested function")
1129}
1130
1131pub fn break_in_recover_block(span: Span) -> LisetteDiagnostic {
1132    LisetteDiagnostic::error("`break` in `recover` block")
1133        .with_infer_code("recover_block_break")
1134        .with_span_label(&span, "not allowed inside `recover` block")
1135        .with_help("Remove the `break`, or move it inside a loop within the `recover` block")
1136}
1137
1138pub fn continue_in_recover_block(span: Span) -> LisetteDiagnostic {
1139    LisetteDiagnostic::error("`continue` in `recover` block")
1140        .with_infer_code("recover_block_continue")
1141        .with_span_label(&span, "not allowed inside `recover` block")
1142        .with_help("Remove the `continue`, or move it inside a loop within the `recover` block")
1143}
1144
1145pub fn expected_channel_receive(ty: &Type, span: Span) -> LisetteDiagnostic {
1146    LisetteDiagnostic::error("Expected channel receive")
1147        .with_infer_code("expected_channel_receive")
1148        .with_span_label(&span, format!("`{}` is not a channel receive", ty))
1149        .with_help("Use `ch.receive()` to receive from a channel in select")
1150}
1151
1152pub fn empty_select(span: Span) -> LisetteDiagnostic {
1153    LisetteDiagnostic::error("Empty select")
1154        .with_infer_code("empty_select")
1155        .with_span_label(&span, "select has no arms")
1156        .with_help(
1157            "Add at least one channel operation arm, e.g. `select { ch.receive() => v { ... } }`",
1158        )
1159}
1160
1161pub fn expected_channel_send(span: Span) -> LisetteDiagnostic {
1162    LisetteDiagnostic::error("Expected channel operation")
1163        .with_infer_code("expected_channel_send")
1164        .with_span_label(&span, "not a channel operation")
1165        .with_help("Use `ch.send(value)` or `ch.receive()` in select arms")
1166}
1167
1168pub fn bare_identifier_in_select_receive(span: &Span) -> LisetteDiagnostic {
1169    LisetteDiagnostic::error("Invalid select case")
1170        .with_infer_code("bare_identifier_in_select_receive")
1171        .with_span_label(span, "expected destructuring")
1172        .with_help("`ch.receive()` returns an `Option`, so use `let Some(v) = ch.receive()` to bind the value")
1173}
1174
1175pub fn none_pattern_in_select_receive(span: &Span) -> LisetteDiagnostic {
1176    LisetteDiagnostic::error("Invalid select case")
1177        .with_infer_code("none_pattern_in_select_receive")
1178        .with_span_label(span, "expected match")
1179        .with_help(
1180            "To detect channel close, use `match ch.receive() { Some(v) => ..., None => ... }`",
1181        )
1182}
1183
1184pub fn select_match_missing_some_arm(span: Span) -> LisetteDiagnostic {
1185    LisetteDiagnostic::error("Invalid select match")
1186        .with_infer_code("select_match_missing_some_arm")
1187        .with_span_label(&span, "missing `Some` arm")
1188        .with_help("`None` only handles channel close. Add a `Some(v) => ...` arm to handle received values")
1189}
1190
1191pub fn select_match_missing_none_arm(span: Span) -> LisetteDiagnostic {
1192    LisetteDiagnostic::error("Invalid select match")
1193        .with_infer_code("select_match_missing_none_arm")
1194        .with_span_label(&span, "missing `None` arm")
1195        .with_help("Matching on `ch.receive()` requires handling channel close. Add a `None => ...` arm to handle channel close, or simplify to `let Some(v) = ch.receive() => ...`")
1196}
1197
1198pub fn select_match_duplicate_some_arm(span: Span) -> LisetteDiagnostic {
1199    LisetteDiagnostic::error("Invalid select match")
1200        .with_infer_code("select_match_duplicate_some_arm")
1201        .with_span_label(&span, "duplicate")
1202        .with_help(
1203            "Remove the duplicate `Some` arm. If you need to, use a `match` inside the arm body",
1204        )
1205}
1206
1207pub fn select_match_duplicate_none_arm(span: Span) -> LisetteDiagnostic {
1208    LisetteDiagnostic::error("Invalid select match")
1209        .with_infer_code("select_match_duplicate_none_arm")
1210        .with_span_label(&span, "duplicate")
1211        .with_help(
1212            "Remove the duplicate `None` arm. If you need to, use a `match` inside the arm body",
1213        )
1214}
1215
1216pub fn select_match_guard_not_allowed(span: Span) -> LisetteDiagnostic {
1217    LisetteDiagnostic::error("Invalid select match")
1218        .with_infer_code("select_match_guard_not_allowed")
1219        .with_span_label(&span, "not supported")
1220        .with_help("Match arms inside `select` do not support guards. Move the condition inside the arm body: `Some(v) => { if condition { ... } }`")
1221}
1222
1223pub fn select_match_invalid_pattern(span: Span) -> LisetteDiagnostic {
1224    LisetteDiagnostic::error("Invalid select match")
1225        .with_infer_code("select_match_invalid_pattern")
1226        .with_span_label(&span, "unsupported pattern")
1227        .with_help("Select match arms support only `Some(...)` and `None` patterns")
1228}
1229
1230pub fn select_receive_refutable_pattern(span: Span) -> LisetteDiagnostic {
1231    LisetteDiagnostic::error("Refutable pattern in select receive")
1232        .with_infer_code("select_receive_refutable_pattern")
1233        .with_span_label(&span, "may not match all received values")
1234        .with_help(
1235            "Select receive requires an irrefutable binding like `Some(v)` or `Some(_)`. \
1236             Use a regular `match` inside the arm body to filter values",
1237        )
1238}
1239
1240pub fn multiple_select_receives(first_span: Span, second_span: Span) -> LisetteDiagnostic {
1241    LisetteDiagnostic::error("Invalid select")
1242        .with_infer_code("multiple_select_receives")
1243        .with_span_label(&first_span, "first receive arm")
1244        .with_span_label(&second_span, "second receive arm")
1245        .with_help("Multiple shorthand receive arms can lead to unexpected behavior when a channel closes. Use `match ch.receive() { Some(v) => ..., None => ... }` to handle closes explicitly")
1246}
1247
1248pub fn duplicate_select_default(first_span: Span, second_span: Span) -> LisetteDiagnostic {
1249    LisetteDiagnostic::error("Invalid select")
1250        .with_infer_code("duplicate_select_default")
1251        .with_span_label(&first_span, "first default arm")
1252        .with_span_label(&second_span, "duplicate default arm")
1253        .with_help(
1254            "A select block can have at most one default arm (`_ => ...`). Remove the duplicate.",
1255        )
1256}
1257
1258pub fn non_exhaustive_select_expression(span: Span) -> LisetteDiagnostic {
1259    LisetteDiagnostic::error("Non-exhaustive select expression")
1260        .with_infer_code("non_exhaustive_select_expression")
1261        .with_span_label(&span, "may not produce a value")
1262        .with_help("Add a default arm `_ => ...` to handle closed channels")
1263}
1264
1265pub fn type_must_be_known(span: Span) -> LisetteDiagnostic {
1266    LisetteDiagnostic::error("Uninferrable type")
1267        .with_infer_code("type_not_inferred")
1268        .with_span_label(&span, "cannot be inferred")
1269        .with_help("Add a type annotation to help the compiler infer the type")
1270}
1271
1272pub fn uninferred_binding(name: &str, span: Span) -> LisetteDiagnostic {
1273    LisetteDiagnostic::error("Uninferrable type")
1274        .with_infer_code("type_not_inferred")
1275        .with_span_label(&span, "cannot be inferred")
1276        .with_help(format!(
1277            "Add a type annotation. For example: `let {}: Slice<int> = ...`",
1278            name
1279        ))
1280}
1281
1282pub fn unconstrained_type_param(param_name: &str, span: Span) -> LisetteDiagnostic {
1283    LisetteDiagnostic::error("Unconstrained type parameter")
1284        .with_infer_code("unconstrained_type_param")
1285        .with_span_label(
1286            &span,
1287            format!(
1288                "`{}` is not constrained by parameters or return type",
1289                param_name
1290            ),
1291        )
1292        .with_help(format!(
1293            "Use `{}` in a parameter or return type, or provide an explicit type argument: `func<SomeType>(...)`",
1294            param_name
1295        ))
1296}
1297
1298pub fn slice_index_type_mismatch(index_ty: &Type, span: Span) -> LisetteDiagnostic {
1299    LisetteDiagnostic::error("Type mismatch")
1300        .with_infer_code("slice_index_type_mismatch")
1301        .with_span_label(&span, format!("expected `int`, found `{}`", index_ty))
1302        .with_help(
1303            "Use an integer to index into a `Slice`. For key-value lookup, use a `Map<K, V>`",
1304        )
1305}
1306
1307pub fn only_slices_and_maps_indexable(ty: &Type, span: Span) -> LisetteDiagnostic {
1308    LisetteDiagnostic::error("Not indexable")
1309        .with_infer_code("not_indexable")
1310        .with_span_label(&span, format!("expected `Slice` or `Map`, found `{}`", ty))
1311        .with_help("Only `Slice` and `Map` can be indexed into")
1312}
1313
1314pub fn string_not_indexable(span: Span, receiver: &str) -> LisetteDiagnostic {
1315    LisetteDiagnostic::error("Cannot index into `string`")
1316        .with_infer_code("string_not_indexable")
1317        .with_span_label(&span, "not indexable")
1318        .with_help(format!(
1319            "Use `{receiver}.rune_at(i)` to get a `rune`, or `{receiver}.byte_at(i)` to get a `byte`"
1320        ))
1321}
1322
1323pub fn not_callable(
1324    ty: &Type,
1325    callee_name: Option<&str>,
1326    arg_name: Option<&str>,
1327    span: Span,
1328) -> LisetteDiagnostic {
1329    let type_name = ty.get_name();
1330    let is_type_call = matches!((callee_name, type_name), (Some(c), Some(t)) if c == t);
1331    let is_cast_target = ty.get_underlying().is_some()
1332        || type_name.is_some_and(|n| SimpleKind::from_name(n).is_some());
1333
1334    let help = if is_type_call && is_cast_target {
1335        let subject = arg_name.unwrap_or("value");
1336        format!(
1337            "Use `{} as {}` to cast between types",
1338            subject,
1339            type_name.unwrap()
1340        )
1341    } else {
1342        "Only functions can be called with `()`".to_string()
1343    };
1344
1345    LisetteDiagnostic::error("Not callable")
1346        .with_infer_code("not_callable")
1347        .with_span_label(&span, format!("expected function, found `{}`", ty))
1348        .with_help(help)
1349}
1350
1351pub fn type_conversion_arity(
1352    type_name: &str,
1353    actual_count: usize,
1354    span: Span,
1355) -> LisetteDiagnostic {
1356    LisetteDiagnostic::error("Wrong argument count")
1357        .with_infer_code("type_conversion_arity")
1358        .with_span_label(
1359            &span,
1360            format!("expected 1 argument, found {}", actual_count),
1361        )
1362        .with_help(format!(
1363            "Type conversion `{}(value)` takes exactly one argument — the value to convert",
1364            type_name
1365        ))
1366}
1367
1368#[derive(Debug, Clone)]
1369pub struct InterfaceViolation {
1370    pub interface_name: String,
1371    pub parent_of: Option<String>,
1372    pub missing: Vec<(String, Type)>,
1373    pub incompatible: Vec<(String, Type, Type)>,
1374}
1375
1376pub fn interface_not_implemented(
1377    interface_name: &str,
1378    type_name: &str,
1379    violations: &[InterfaceViolation],
1380    span: Span,
1381) -> LisetteDiagnostic {
1382    let mut help_lines = Vec::new();
1383
1384    let mut missing_sections: Vec<(String, Vec<String>)> = Vec::new();
1385    let mut incompatible_sections: Vec<(String, Vec<String>)> = Vec::new();
1386
1387    for violation in violations {
1388        let header = if let Some(ref parent) = violation.parent_of {
1389            format!(
1390                "From `{}` (required by `{}`)",
1391                violation.interface_name, parent
1392            )
1393        } else {
1394            format!("From `{}`", violation.interface_name)
1395        };
1396
1397        if !violation.missing.is_empty() {
1398            let methods: Vec<String> = violation
1399                .missing
1400                .iter()
1401                .map(|(name, sig)| format!("  - {}: {}", name, sig))
1402                .collect();
1403            missing_sections.push((header.clone(), methods));
1404        }
1405
1406        if !violation.incompatible.is_empty() {
1407            let methods: Vec<String> = violation
1408                .incompatible
1409                .iter()
1410                .map(|(name, expected, actual)| {
1411                    format!("  - {}: found `{}`, expected `{}`", name, actual, expected)
1412                })
1413                .collect();
1414            incompatible_sections.push((header, methods));
1415        }
1416    }
1417
1418    if !missing_sections.is_empty() {
1419        help_lines.push("Missing methods:".to_string());
1420        for (header, methods) in &missing_sections {
1421            help_lines.push(format!("  {}", header));
1422            for method in methods {
1423                help_lines.push(format!("  {}", method));
1424            }
1425        }
1426    }
1427
1428    if !incompatible_sections.is_empty() {
1429        help_lines.push("Incompatible methods:".to_string());
1430        for (header, methods) in &incompatible_sections {
1431            help_lines.push(format!("  {}", header));
1432            for method in methods {
1433                help_lines.push(format!("  {}", method));
1434            }
1435        }
1436    }
1437
1438    LisetteDiagnostic::error("Interface not implemented")
1439        .with_infer_code("interface_not_implemented")
1440        .with_span_label(
1441            &span,
1442            format!("`{}` does not implement `{}`", type_name, interface_name),
1443        )
1444        .with_help(help_lines.join("\n"))
1445}
1446
1447pub fn pointer_receiver_interface_mismatch(
1448    interface_name: &str,
1449    type_name: &str,
1450    methods: &[String],
1451    span: Span,
1452) -> LisetteDiagnostic {
1453    let methods_str = methods
1454        .iter()
1455        .map(|m| format!("`{}.{}`", type_name, m))
1456        .collect::<Vec<_>>()
1457        .join(", ");
1458    let takes = if methods.len() == 1 {
1459        format!("{} takes `self: Ref<{}>`", methods_str, type_name)
1460    } else {
1461        format!("{} take `self: Ref<{}>`", methods_str, type_name)
1462    };
1463    LisetteDiagnostic::error("Interface not implemented")
1464        .with_infer_code("interface_not_implemented")
1465        .with_span_label(
1466            &span,
1467            format!("`{}` does not implement `{}`", type_name, interface_name),
1468        )
1469        .with_help(format!("{}, so pass a `Ref<{}>`.", takes, type_name))
1470}
1471
1472pub fn unknown_outside_typedef(span: Span) -> LisetteDiagnostic {
1473    LisetteDiagnostic::error("Invalid `Unknown` type")
1474        .with_infer_code("unknown_outside_typedef")
1475        .with_span_label(&span, "not allowed in `.lis` files")
1476        .with_help("Remove the `Unknown` annotation.")
1477        .with_note("`Unknown` can only be used in `.d.lis` files. This type exists only to represent Go's `any` in typedefs.")
1478}
1479
1480pub fn opaque_type_outside_typedef(span: Span) -> LisetteDiagnostic {
1481    LisetteDiagnostic::error("Undefined type")
1482        .with_infer_code("undefined_type_outside_typedef")
1483        .with_span_label(&span, "needs a definition")
1484        .with_help("Use `type Point = ...` to define the type.")
1485        .with_note("Opaque declarations are only allowed in `.d.lis` files.")
1486}
1487
1488pub fn bodyless_function_outside_typedef(span: Span) -> LisetteDiagnostic {
1489    LisetteDiagnostic::error("Missing function body")
1490        .with_infer_code("bodyless_function_outside_typedef")
1491        .with_span_label(&span, "needs a body")
1492        .with_help("Add a body: `fn greet() { ... }`.")
1493        .with_note("Bodyless declarations are only allowed in `.d.lis` files.")
1494}
1495
1496pub fn valueless_const_outside_typedef(span: Span) -> LisetteDiagnostic {
1497    LisetteDiagnostic::error("Missing const value")
1498        .with_infer_code("valueless_const_outside_typedef")
1499        .with_span_label(&span, "needs a value")
1500        .with_help("Ensure the constant has a value: `const MAX_SIZE: int = 100`.")
1501        .with_note("Valueless const declarations are only allowed in `.d.lis` files.")
1502}
1503
1504pub fn valueless_const_missing_annotation(span: Span) -> LisetteDiagnostic {
1505    LisetteDiagnostic::error("Missing const annotation")
1506        .with_infer_code("valueless_const_missing_annotation")
1507        .with_span_label(&span, "needs a type annotation")
1508        .with_help("Value-less const declarations require a type annotation: `const MAX_SIZE: int`")
1509}
1510
1511pub fn variable_declaration_outside_typedef(span: Span) -> LisetteDiagnostic {
1512    LisetteDiagnostic::error("Invalid variable declaration")
1513        .with_infer_code("variable_declaration_outside_typedef")
1514        .with_span_label(&span, "`var` is not allowed here")
1515        .with_help("Use `let` to declare a variable: `let x: int = 0`.")
1516        .with_note("`var` declarations are only allowed in `.d.lis` files.")
1517}
1518
1519pub fn range_full_not_valid_expression(span: Span) -> LisetteDiagnostic {
1520    LisetteDiagnostic::error("Invalid expression")
1521        .with_infer_code("range_full_not_expression")
1522        .with_span_label(&span, "`..` can only be used in slice indexing")
1523        .with_help("Use `arr[..]` to get a full slice, or provide bounds like `0..10`")
1524}
1525
1526pub fn range_not_iterable(range_type: &str, span: Span) -> LisetteDiagnostic {
1527    LisetteDiagnostic::error("Not iterable")
1528        .with_infer_code("range_not_iterable")
1529        .with_span_label(&span, format!("`{}` has no start bound", range_type))
1530        .with_help("Use a range with a start bound, e.g. `0..10` instead of `..10`")
1531}
1532
1533pub fn taking_value_of_ufcs_method(span: Span) -> LisetteDiagnostic {
1534    LisetteDiagnostic::error("Invalid method value")
1535        .with_infer_code("taking_value_of_ufcs_method")
1536        .with_span_label(&span, "taking value not allowed")
1537        .with_help(
1538            "This method cannot be taken as a value. Call the method directly: `obj.method(...)`",
1539        )
1540}
1541
1542pub fn duplicate_definition(kind: &str, name: &str, span: Span) -> LisetteDiagnostic {
1543    LisetteDiagnostic::error(format!("Duplicate {}", kind))
1544        .with_infer_code("duplicate_definition")
1545        .with_span_label(&span, "already defined")
1546        .with_help(format!(
1547            "`{}` is already defined in this module. Rename or remove this definition.",
1548            name
1549        ))
1550}
1551
1552pub fn duplicate_impl_item(item_name: &str, type_name: &str, span: Span) -> LisetteDiagnostic {
1553    LisetteDiagnostic::error("Duplicate name in impl")
1554        .with_infer_code("duplicate_impl_item")
1555        .with_span_label(&span, "method name already taken")
1556        .with_help(format!(
1557            "Method `{}` is already defined for type `{}`. Rename one of the methods.",
1558            item_name, type_name
1559        ))
1560}
1561
1562pub fn duplicate_method_across_specialized_impls(
1563    method_name: &str,
1564    type_name: &str,
1565    generics: &[String],
1566    span: Span,
1567) -> LisetteDiagnostic {
1568    let params = generics.join(", ");
1569    LisetteDiagnostic::error("Duplicate method across specialized `impl` blocks")
1570        .with_infer_code("duplicate_method_across_specialized_impls")
1571        .with_span_label(&span, "already defined in another specialization")
1572        .with_help(format!(
1573            "Specialized `impl` blocks for `{type_name}` share a method namespace. \
1574             Use different method names, or move `{method_name}` to a generic `impl<{params}> {type_name}<{params}> {{}}` block."
1575        ))
1576}
1577
1578pub fn method_shadows_field(type_name: &str, field_name: &str, span: Span) -> LisetteDiagnostic {
1579    LisetteDiagnostic::error("Method shadows struct field")
1580        .with_infer_code("method_shadows_field")
1581        .with_span_label(&span, "same as field")
1582        .with_help(format!(
1583            "`{}` has a field `{}` and a method `{}`. Rename either the field or the method",
1584            type_name, field_name, field_name
1585        ))
1586}
1587
1588pub fn non_int_range_not_iterable(element_ty: &Type, span: Span) -> LisetteDiagnostic {
1589    LisetteDiagnostic::error("Not iterable")
1590        .with_infer_code("non_int_range_not_iterable")
1591        .with_span_label(
1592            &span,
1593            format!("cannot iterate over `Range<{}>`", element_ty),
1594        )
1595        .with_help("Range iteration requires integer bounds")
1596}
1597
1598pub fn only_slices_indexable_by_range(ty: &Type, span: &Span) -> LisetteDiagnostic {
1599    LisetteDiagnostic::error("Type mismatch")
1600        .with_infer_code("range_index_not_slice")
1601        .with_span_label(
1602            span,
1603            format!("expected `Slice` or `string`, found `{}`", ty),
1604        )
1605        .with_help("Range indexing only works on `Slice` and `string`")
1606}
1607
1608pub fn empty_body_return_mismatch(expected_ty: &Type, span: Span) -> LisetteDiagnostic {
1609    LisetteDiagnostic::error("Type mismatch")
1610        .with_infer_code("type_mismatch")
1611        .with_span_label(
1612            &span,
1613            format!("promises `{}`, but returns `()`", expected_ty),
1614        )
1615        .with_help("Return a value or change the return type annotation to `()`.")
1616        .with_note("An empty function body implicitly returns `()`.")
1617}
1618
1619fn operator_verb(operator: &BinaryOperator) -> &'static str {
1620    match operator {
1621        BinaryOperator::Addition => "add",
1622        BinaryOperator::Subtraction => "subtract",
1623        BinaryOperator::Multiplication => "multiply",
1624        BinaryOperator::Division => "divide",
1625        BinaryOperator::Remainder => "get remainder of",
1626        BinaryOperator::Equal | BinaryOperator::NotEqual => "compare",
1627        BinaryOperator::LessThan
1628        | BinaryOperator::LessThanOrEqual
1629        | BinaryOperator::GreaterThan
1630        | BinaryOperator::GreaterThanOrEqual => "compare",
1631        BinaryOperator::And | BinaryOperator::Or => "apply logical operator to",
1632        BinaryOperator::Pipeline => "pipe",
1633    }
1634}
1635
1636fn operator_help(op: &BinaryOperator) -> &'static str {
1637    match op {
1638        BinaryOperator::Addition => "requires both operands to have the same type",
1639        BinaryOperator::Subtraction
1640        | BinaryOperator::Multiplication
1641        | BinaryOperator::Division
1642        | BinaryOperator::Remainder => "requires both operands to have the same numeric type",
1643        BinaryOperator::Equal | BinaryOperator::NotEqual => {
1644            "requires both operands to have the same type"
1645        }
1646        BinaryOperator::LessThan
1647        | BinaryOperator::LessThanOrEqual
1648        | BinaryOperator::GreaterThan
1649        | BinaryOperator::GreaterThanOrEqual => "requires both operands to have the same type",
1650        BinaryOperator::And | BinaryOperator::Or => "requires both operands to be bool",
1651        BinaryOperator::Pipeline => "should have been desugared",
1652    }
1653}
1654
1655pub fn task_in_expression_position(span: Span) -> LisetteDiagnostic {
1656    LisetteDiagnostic::error("Invalid `task`")
1657        .with_infer_code("task_in_expression_position")
1658        .with_span_label(&span, "produces no value")
1659        .with_help("Move `task` to its own statement")
1660}
1661
1662pub fn defer_in_expression_position(span: Span) -> LisetteDiagnostic {
1663    LisetteDiagnostic::error("Invalid `defer`")
1664        .with_infer_code("defer_in_expression_position")
1665        .with_span_label(&span, "produces no value")
1666        .with_help("Move `defer` to its own statement")
1667}
1668
1669pub fn non_addressable_expression(expression_kind: &str, span: Span) -> LisetteDiagnostic {
1670    LisetteDiagnostic::error("Non-addressable expression")
1671        .with_infer_code("non_addressable_expression")
1672        .with_span_label(&span, format!("cannot take address of {}", expression_kind))
1673        .with_help("Assign the value to a variable first, then take its address")
1674}
1675
1676pub fn non_addressable_const(span: Span) -> LisetteDiagnostic {
1677    LisetteDiagnostic::error("Cannot take address of `const`")
1678        .with_infer_code("non_addressable_const")
1679        .with_span_label(&span, "not addressable")
1680        .with_help(
1681            "`const` bindings are not addressable. Copy the value into a local `let` first if you need a reference",
1682        )
1683}
1684
1685pub fn non_addressable_assignment(expression_kind: &str, span: Span) -> LisetteDiagnostic {
1686    LisetteDiagnostic::error("Cannot assign to non-addressable expression")
1687        .with_infer_code("non_addressable_assignment")
1688        .with_span_label(&span, format!("cannot assign to {}", expression_kind))
1689        .with_help("Assign the value to a variable first, then modify it")
1690}
1691
1692pub fn newtype_field_assignment(type_name: &str, span: Span) -> LisetteDiagnostic {
1693    LisetteDiagnostic::error("Cannot assign to newtype field")
1694        .with_infer_code("newtype_field_assignment")
1695        .with_span_label(&span, "newtype fields are read-only")
1696        .with_help(format!(
1697            "Reconstruct the newtype: `variable = {type_name}(new_value)`"
1698        ))
1699}
1700
1701pub fn complex_select_expression(span: Span) -> LisetteDiagnostic {
1702    LisetteDiagnostic::error("Complex expression in `select` arm")
1703        .with_infer_code("complex_select_expression")
1704        .with_span_label(&span, "expected simple expression")
1705        .with_help("Hoist to a `let` binding before the `select`")
1706}
1707
1708pub fn ref_slice_append(span: Span) -> LisetteDiagnostic {
1709    LisetteDiagnostic::error("Cannot call append/extend on `Ref<Slice>`")
1710        .with_infer_code("ref_slice_append")
1711        .with_span_label(&span, "dereference the ref first")
1712        .with_help("Use `r.*.append(x)` to deref, then append")
1713}
1714
1715pub fn map_field_chain_assignment(span: Span) -> LisetteDiagnostic {
1716    LisetteDiagnostic::error("Cannot assign to field of map entry")
1717        .with_infer_code("map_field_chain_assignment")
1718        .with_span_label(&span, "assignment not allowed here")
1719        .with_help(
1720            "Extract, modify, and reinsert: `let mut entry = m[key]; entry.field = value; m[key] = entry`",
1721        )
1722}
1723
1724pub fn enum_field_type_conflict(
1725    loc_a: &str,
1726    type_a: &str,
1727    loc_b: &str,
1728    type_b: &str,
1729    span: Span,
1730) -> LisetteDiagnostic {
1731    LisetteDiagnostic::error("Conflicting field types across enum variants")
1732        .with_infer_code("enum_field_type_conflict")
1733        .with_span_label(&span, "field type mismatch")
1734        .with_help(format!(
1735            "`{loc_a}` is `{type_a}` but `{loc_b}` is `{type_b}`. Rename one of the fields or align their types",
1736        ))
1737}
1738
1739pub fn cannot_auto_address_receiver(
1740    receiver_kind: &str,
1741    method_name: &str,
1742    expected_ty: &Type,
1743    actual_ty: &Type,
1744    span: Span,
1745) -> LisetteDiagnostic {
1746    let readable_kind = match receiver_kind {
1747        "map index expression" => "map lookup",
1748        "function call" => "function result",
1749        "literal" => "literal",
1750        "binary expression" => "expression result",
1751        "conditional expression" => "conditional result",
1752        "match expression" => "match result",
1753        "block expression" => "block result",
1754        "lambda" => "lambda",
1755        "tuple" => "tuple",
1756        "range expression" => "range expression",
1757        _ => "expression",
1758    };
1759
1760    LisetteDiagnostic::error("Expression not modifiable")
1761        .with_infer_code("cannot_auto_address_receiver")
1762        .with_span_label(&span, "modifies its receiver")
1763        .with_help(format!(
1764            "Assign the {} to a variable first, then call the method. The receiver of `{}` is `{}`, not `{}`",
1765            readable_kind, method_name, expected_ty, actual_ty
1766        ))
1767}
1768
1769pub fn break_value_in_non_loop(span: Span) -> LisetteDiagnostic {
1770    LisetteDiagnostic::error("`break` with value in non-`loop` loop")
1771        .with_infer_code("break_value_in_non_loop")
1772        .with_span_label(&span, "`break` with value only allowed in `loop`")
1773        .with_help("`break` with a value is only meaningful in `loop` expressions, which can return the value. In `for` and `while` loops, use `break` without a value.")
1774}
1775
1776pub fn defer_in_loop(span: Span) -> LisetteDiagnostic {
1777    LisetteDiagnostic::error("`defer` inside loop")
1778        .with_infer_code("defer_in_loop")
1779        .with_span_label(&span, "not allowed inside loop")
1780        .with_help("Wrap the loop body in a helper function, e.g. `fn process(file: File) { defer file.close(); ... }` and call it in the loop: `for f in files { process(f); }`")
1781}
1782
1783pub fn propagate_in_condition(span: Span) -> LisetteDiagnostic {
1784    LisetteDiagnostic::error("`?` cannot be used inside a condition")
1785        .with_infer_code("propagate_in_condition")
1786        .with_span_label(&span, "`?` inside condition")
1787        .with_help("Bind the result first: `let val = expression?; if val { ... }`")
1788}
1789
1790pub fn propagate_in_defer(span: Span) -> LisetteDiagnostic {
1791    LisetteDiagnostic::error("Invalid `?` in `defer`")
1792        .with_infer_code("propagate_in_defer")
1793        .with_span_label(&span, "`?` not allowed here")
1794        .with_help("`defer` in combination with `?` is not allowed due to confusing semantics. Handle the error inside a `defer` block: `defer { if let Err(e) = file.close() { log(e); } }`")
1795}
1796
1797pub fn return_in_defer_block(span: Span) -> LisetteDiagnostic {
1798    LisetteDiagnostic::error("`return` in `defer` block")
1799        .with_infer_code("return_in_defer_block")
1800        .with_span_label(&span, "not allowed inside `defer` block")
1801        .with_help("Remove the `return` as it only exits the `defer` block")
1802}
1803
1804pub fn break_in_defer_block(span: Span) -> LisetteDiagnostic {
1805    LisetteDiagnostic::error("`break` in `defer` block")
1806        .with_infer_code("break_in_defer_block")
1807        .with_span_label(&span, "not allowed inside `defer` block")
1808        .with_help("Remove the `break`, or move it inside a loop within the `defer` block")
1809}
1810
1811pub fn continue_in_defer_block(span: Span) -> LisetteDiagnostic {
1812    LisetteDiagnostic::error("`continue` in `defer` block")
1813        .with_infer_code("continue_in_defer_block")
1814        .with_span_label(&span, "not allowed inside `defer` block")
1815        .with_help("Remove the `continue`, or move it inside a loop within the `defer` block")
1816}
1817
1818pub fn invalid_cast(source_ty: &Type, target_ty: &Type, span: Span) -> LisetteDiagnostic {
1819    let same_constructor_with_unresolved = source_ty
1820        .get_qualified_id()
1821        .zip(target_ty.get_qualified_id())
1822        .is_some_and(|(s, t)| s == t)
1823        && source_ty.has_unbound_variables();
1824
1825    let help = if same_constructor_with_unresolved {
1826        format!(
1827            "Use a type annotation instead: `let x: {} = ...`",
1828            target_ty,
1829        )
1830    } else if source_ty.is_string() {
1831        "Strings cannot be cast to numbers and require explicit conversion. Use `strconv.Atoi()` to parse.".into()
1832    } else if source_ty.is_complex() || target_ty.is_complex() {
1833        "Complex numbers cannot be cast directly. Use `real(c)` or `imaginary(c)` to extract components.".into()
1834    } else {
1835        "Casts are supported between numeric types, strings and byte/rune slices, concrete types and interfaces, and values and their type aliases.".into()
1836    };
1837
1838    LisetteDiagnostic::error("Invalid cast")
1839        .with_infer_code("invalid_cast")
1840        .with_span_label(
1841            &span,
1842            format!("cannot cast `{}` to `{}`", source_ty, target_ty),
1843        )
1844        .with_help(help)
1845}
1846
1847pub fn chained_cast(span: Span) -> LisetteDiagnostic {
1848    LisetteDiagnostic::error("Invalid cast")
1849        .with_infer_code("chained_cast")
1850        .with_span_label(&span, "chained cast not allowed")
1851        .with_help("Use an intermediate variable if you need to cast through multiple types")
1852}
1853
1854pub fn redundant_cast(ty: &Type, span: Span) -> LisetteDiagnostic {
1855    LisetteDiagnostic::warn("Redundant cast")
1856        .with_infer_code("redundant_cast")
1857        .with_span_label(&span, format!("casting `{}` to itself has no effect", ty))
1858        .with_help("Remove the unnecessary cast")
1859}
1860
1861pub fn integer_literal_overflow(
1862    target_ty: &str,
1863    min: i128,
1864    max: i128,
1865    span: Span,
1866) -> LisetteDiagnostic {
1867    LisetteDiagnostic::error("Integer literal overflow")
1868        .with_infer_code("integer_literal_overflow")
1869        .with_span_label(&span, format!("overflows `{}`", target_ty))
1870        .with_help(format!(
1871            "`{}` must be in range `{}` to `{}`",
1872            target_ty, min, max
1873        ))
1874}
1875
1876pub fn float_literal_overflow(target_ty: &str, span: Span) -> LisetteDiagnostic {
1877    LisetteDiagnostic::error("Float literal overflow")
1878        .with_infer_code("float_literal_overflow")
1879        .with_span_label(&span, format!("value overflows `{}`", target_ty))
1880        .with_help(format!(
1881            "Use `float64` for larger values, or ensure the value fits in `{}`",
1882            target_ty
1883        ))
1884}
1885
1886pub fn cannot_negate_unsigned(target_ty: &str, span: Span) -> LisetteDiagnostic {
1887    LisetteDiagnostic::error("Cannot negate unsigned type")
1888        .with_infer_code("cannot_negate_unsigned")
1889        .with_span_label(&span, format!("cannot negate `{}`", target_ty))
1890        .with_help("Unsigned types cannot represent negative values")
1891}
1892
1893fn go_builtin_hint(name: &str) -> Option<&'static str> {
1894    match name {
1895        "len" => Some(
1896            "Lisette has no `len` builtin. Use the `.length()` method instead, e.g. `items.length()`.",
1897        ),
1898        "cap" => Some(
1899            "Lisette has no `cap` builtin. Use the `.capacity()` method instead, e.g. `items.capacity()`.",
1900        ),
1901        "make" => Some(
1902            "Lisette has no `make` builtin. Use constructor methods instead, e.g. `Channel.new<int>()`, `Slice.new<int>()`, `Map.new<K, V>()`.",
1903        ),
1904        "append" => Some(
1905            "Lisette has no `append` builtin. Use the `.append()` method instead, e.g. `items.append(1)`.",
1906        ),
1907        "close" => Some(
1908            "Lisette has no `close` builtin. Use the `.close()` method instead, e.g. `ch.close()`.",
1909        ),
1910        "copy" => Some(
1911            "Lisette has no `copy` builtin. Use the `.copy_from()` method instead, e.g. `dst.copy_from(src)`.",
1912        ),
1913        "delete" => Some(
1914            "Lisette has no `delete` builtin. Use the `.delete()` method instead, e.g. `map.delete(key)`.",
1915        ),
1916        "new" => Some(
1917            "Lisette has no `new` builtin. Use constructor methods instead, e.g. `MyStruct { field: value }` or `MyType.new()`.",
1918        ),
1919        "print" | "println" | "printf" => Some(
1920            "Lisette has no `print` builtin. Use `fmt.Println`, `fmt.Printf`, etc. after `import \"go:fmt\"`.",
1921        ),
1922        _ => None,
1923    }
1924}
1925
1926pub fn levenshtein_distance(a: &str, b: &str) -> usize {
1927    let b_len = b.len();
1928
1929    if a.is_empty() {
1930        return b_len;
1931    }
1932    if b_len == 0 {
1933        return a.len();
1934    }
1935
1936    let mut prev: Vec<usize> = (0..=b_len).collect();
1937    let mut curr = vec![0; b_len + 1];
1938
1939    for (i, a_char) in a.chars().enumerate() {
1940        curr[0] = i + 1;
1941        for (j, b_char) in b.chars().enumerate() {
1942            let cost = if a_char == b_char { 0 } else { 1 };
1943            curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
1944        }
1945        std::mem::swap(&mut prev, &mut curr);
1946    }
1947
1948    prev[b_len]
1949}
1950
1951/// Uses Levenshtein distance (threshold <= 2) and prefix matching
1952/// to catch abbreviations like `len` → `length`.
1953pub fn find_similar_name(name: &str, candidates: &[String]) -> Option<String> {
1954    let best_distance = candidates
1955        .iter()
1956        .filter_map(|c| {
1957            let d = levenshtein_distance(name, c);
1958            (d <= 2).then_some((c, d))
1959        })
1960        .min_by_key(|(_, d)| *d);
1961
1962    let by_prefix = if name.len() >= 2 {
1963        candidates
1964            .iter()
1965            .filter(|c| c.starts_with(name) || name.starts_with(c.as_str()))
1966            .min_by_key(|c| c.len().abs_diff(name.len()))
1967    } else {
1968        None
1969    };
1970
1971    match (best_distance, by_prefix) {
1972        (Some((d, dist)), Some(p)) => {
1973            // Prefer Levenshtein only if it's a very close match (distance 1),
1974            // otherwise prefer prefix which better handles abbreviations
1975            if dist <= 1 {
1976                Some(d.clone())
1977            } else {
1978                Some(p.clone())
1979            }
1980        }
1981        (Some((d, _)), None) => Some(d.clone()),
1982        (None, Some(p)) => Some(p.clone()),
1983        (None, None) => None,
1984    }
1985}
1986
1987pub fn cannot_infer_type_argument(span: Span) -> LisetteDiagnostic {
1988    LisetteDiagnostic::error("Missing type argument")
1989        .with_infer_code("missing_type_argument")
1990        .with_span_label(&span, "expected type argument")
1991        .with_help("Supply a type argument for the call, e.g. `Channel.new<int>()`")
1992}
1993
1994fn format_list<T, F>(items: &[T], fmt: F) -> String
1995where
1996    F: Fn(&T) -> String,
1997{
1998    match items.len() {
1999        0 => String::new(),
2000        1 => fmt(&items[0]),
2001        2 => format!("{} and {}", fmt(&items[0]), fmt(&items[1])),
2002        _ => {
2003            let mut result = String::new();
2004            for (i, item) in items.iter().enumerate() {
2005                if i > 0 {
2006                    result.push_str(", ");
2007                }
2008                if i == items.len() - 1 {
2009                    result.push_str("and ");
2010                }
2011                result.push_str(&fmt(item));
2012            }
2013            result
2014        }
2015    }
2016}
2017
2018pub fn recursive_generic_instantiation(type_name: &str, span: Span) -> LisetteDiagnostic {
2019    LisetteDiagnostic::error("Recursive generic instantiation")
2020        .with_infer_code("recursive_instantiation")
2021        .with_span_label(&span, format!("`{}` is nested within itself", type_name))
2022        .with_help(format!(
2023            "Go does not allow recursive type instantiation (e.g., `{0}<{0}<T>>`). \
2024             Use a wrapper type or a different design.",
2025            type_name
2026        ))
2027}
2028
2029pub fn non_comparable_map_key(key_ty: &Type, reason: &str, span: Span) -> LisetteDiagnostic {
2030    LisetteDiagnostic::error("Invalid map key type")
2031        .with_infer_code("non_comparable_map_key")
2032        .with_span_label(&span, format!("`{}` is not comparable", key_ty))
2033        .with_help(format!(
2034            "Map keys must be comparable in Go. {} cannot be used as map keys.",
2035            reason
2036        ))
2037}
2038
2039pub fn ref_of_interface_type(inner_ty: &Type, span: Span) -> LisetteDiagnostic {
2040    LisetteDiagnostic::error("Invalid use of `Ref` with interface")
2041        .with_infer_code("ref_of_interface")
2042        .with_span_label(&span, "not allowed")
2043        .with_help(format!(
2044            "Use `{}` instead of `Ref<{}>`. Interfaces are already reference types in Go.",
2045            inner_ty, inner_ty
2046        ))
2047}
2048
2049pub fn float_modulo_not_supported(span: Span) -> LisetteDiagnostic {
2050    LisetteDiagnostic::error("Invalid operation")
2051        .with_infer_code("float_modulo")
2052        .with_span_label(&span, "`%` is not supported on floating-point types")
2053        .with_help("Use `math.Mod(x, y)` for floating-point modulo")
2054}
2055
2056pub fn recursive_type(type_name: &str, span: Span) -> LisetteDiagnostic {
2057    LisetteDiagnostic::error("Recursive type has infinite size")
2058        .with_infer_code("recursive_type")
2059        .with_span_label(
2060            &span,
2061            format!("`{}` contains itself without indirection", type_name),
2062        )
2063        .with_help(format!(
2064            "Use `Ref<{}>` for indirection. For example: `next: Option<Ref<{}>>`",
2065            type_name, type_name
2066        ))
2067}
2068
2069pub fn interface_self_embedding(interface_name: &str, span: Span) -> LisetteDiagnostic {
2070    LisetteDiagnostic::error("Recursive interface embedding")
2071        .with_infer_code("interface_cycle")
2072        .with_span_label(&span, format!("`{}` embeds itself", interface_name))
2073        .with_help("An interface cannot embed itself. Remove the self-referencing `impl`.")
2074}
2075
2076pub fn interface_embedding_cycle(cycle: &[String], span: Span) -> LisetteDiagnostic {
2077    let cycle_str = cycle.join(" → ");
2078    LisetteDiagnostic::error("Recursive interface embedding")
2079        .with_infer_code("interface_cycle")
2080        .with_span_label(&span, "creates a cycle")
2081        .with_help(format!(
2082            "Interface embedding cycle detected: {}. Break the cycle by removing one of the embeddings.",
2083            cycle_str
2084        ))
2085}
2086
2087pub fn interface_method_conflict(
2088    interface_name: &str,
2089    method_name: &str,
2090    parent1: &str,
2091    parent2: &str,
2092    span: Span,
2093) -> LisetteDiagnostic {
2094    LisetteDiagnostic::error("Conflicting method signatures")
2095        .with_infer_code("interface_method_conflict")
2096        .with_span_label(&span, format!("duplicate method `{}`", method_name))
2097        .with_help(format!(
2098            "Interface `{}` inherits conflicting definitions of `{}` from `{}` and `{}`. \
2099             Rename one of the methods or remove one of the embeddings.",
2100            interface_name, method_name, parent1, parent2
2101        ))
2102}
2103
2104pub fn impl_on_foreign_type(type_name: &str, module_name: &str, span: Span) -> LisetteDiagnostic {
2105    LisetteDiagnostic::error("Cannot implement methods on foreign type")
2106        .with_infer_code("impl_on_foreign_type")
2107        .with_span_label(
2108            &span,
2109            format!("`{}` is defined in module `{}`", type_name, module_name),
2110        )
2111        .with_help(format!(
2112            "Methods can only be defined on types in the same module. \
2113             Use a standalone function instead: `fn my_method(w: {}) {{ ... }}`",
2114            type_name
2115        ))
2116}
2117
2118pub fn impl_on_type_alias(_type_name: &str, span: Span) -> LisetteDiagnostic {
2119    LisetteDiagnostic::error("Cannot implement methods on type alias")
2120        .with_infer_code("impl_on_type_alias")
2121        .with_span_label(&span, "type alias")
2122        .with_help(
2123            "A type alias cannot carry its own methods. Either add methods to the underlying \
2124             type directly or define a tuple struct instead",
2125        )
2126}
2127
2128pub fn prelude_type_shadowed(name: &str, span: Span) -> LisetteDiagnostic {
2129    LisetteDiagnostic::error("Cannot shadow prelude type")
2130        .with_infer_code("prelude_type_shadowed")
2131        .with_span_label(&span, format!("`{}` is a prelude type", name))
2132        .with_help(format!(
2133            "Choose a different name — `{}` is defined in the prelude and cannot be redefined",
2134            name
2135        ))
2136}
2137
2138pub fn prelude_function_shadowed(name: &str, span: Span) -> LisetteDiagnostic {
2139    LisetteDiagnostic::error("Cannot shadow prelude function")
2140        .with_infer_code("prelude_function_shadowed")
2141        .with_span_label(&span, format!("`{}` is a prelude function", name))
2142        .with_help(format!(
2143            "Choose a different name — `{}` is defined in the prelude and cannot be redefined",
2144            name
2145        ))
2146}
2147
2148pub fn non_pub_interface_with_pub_impl(
2149    interface_name: &str,
2150    struct_name: &str,
2151    span: Span,
2152) -> LisetteDiagnostic {
2153    LisetteDiagnostic::error("Visibility mismatch in interface implementation")
2154        .with_infer_code("non_pub_interface_pub_impl")
2155        .with_span_label(
2156            &span,
2157            "has public methods, but interface is private",
2158        )
2159        .with_help(format!(
2160            "`{}` implements public methods for the private interface `{}`. Either make the interface `pub`, or remove `pub` from the struct methods",
2161            struct_name, interface_name
2162        ))
2163}
2164
2165pub fn missing_constraint_on_generic_return_type(
2166    fn_name: &str,
2167    param_name: &str,
2168    constraint: &Type,
2169    span: Span,
2170) -> LisetteDiagnostic {
2171    LisetteDiagnostic::error("Missing constraint on generic return type")
2172        .with_infer_code("missing_constraint_on_return_type")
2173        .with_span_label(
2174            &span,
2175            format!("expected `{}` to be constrained", param_name),
2176        )
2177        .with_help(
2178            format!(
2179                "Constrain the generic: `{}<{}: {}>()`",
2180                fn_name, param_name, constraint
2181            ) + ". The function returns a type whose methods depend on the constraint",
2182        )
2183}
2184
2185pub fn panic_in_expression_position(span: Span) -> LisetteDiagnostic {
2186    LisetteDiagnostic::error("`panic()` used as a value")
2187        .with_infer_code("panic_in_expression_position")
2188        .with_span_label(&span, "disallowed")
2189        .with_help("`panic()` can only be used in statement position, not assigned to a variable or passed as an argument")
2190}
2191
2192pub fn specialized_impl_cannot_satisfy_interface(
2193    struct_name: &str,
2194    interface_name: &str,
2195    method_name: &str,
2196    generics: &[String],
2197    span: Span,
2198) -> LisetteDiagnostic {
2199    let params = generics.join(", ");
2200    LisetteDiagnostic::error("Specialized impl cannot satisfy interface")
2201        .with_infer_code("specialized_impl_cannot_satisfy_interface")
2202        .with_span_label(
2203            &span,
2204            format!(
2205                "`{}` on `{}` cannot satisfy `{}`",
2206                method_name, struct_name, interface_name
2207            ),
2208        )
2209        .with_help(format!(
2210            "Methods in specialized `impl` blocks cannot satisfy interfaces. \
2211             Move `{}` to a generic `impl` block: `impl<{params}> {}<{params}> {{}}`",
2212            method_name, struct_name
2213        ))
2214}
2215
2216pub fn native_method_value(method: &str, span: Span) -> LisetteDiagnostic {
2217    LisetteDiagnostic::error("Cannot use native method as a value")
2218        .with_infer_code("native_method_value")
2219        .with_span_label(&span, "native methods must be called directly")
2220        .with_help(format!(
2221            "Use a closure instead: `|args| receiver.{}(args)`",
2222            method
2223        ))
2224}
2225
2226pub fn native_constructor_value(name: &str, span: Span) -> LisetteDiagnostic {
2227    LisetteDiagnostic::error("Cannot use native constructor as a value")
2228        .with_infer_code("native_constructor_value")
2229        .with_span_label(&span, "native constructors must be called directly")
2230        .with_help(format!("Use a closure instead: `|args| {name}(args)`"))
2231}
2232
2233pub fn enum_variant_constructor_value(name: &str, span: Span) -> LisetteDiagnostic {
2234    LisetteDiagnostic::error("Cannot use enum variant as value")
2235        .with_infer_code("enum_variant_constructor_value")
2236        .with_span_label(&span, "used as value")
2237        .with_help(format!(
2238            "Instantiate the variant: `{name} {{ field: value, ... }}`"
2239        ))
2240}
2241
2242pub fn record_struct_value(name: &str, span: Span) -> LisetteDiagnostic {
2243    LisetteDiagnostic::error("Cannot use struct type as a value")
2244        .with_infer_code("record_struct_value")
2245        .with_span_label(&span, "struct types cannot be used as expressions")
2246        .with_help(format!(
2247            "Use a struct literal instead: `{name} {{ field: value, ... }}`"
2248        ))
2249}
2250
2251pub fn private_method_expression(span: Span) -> LisetteDiagnostic {
2252    LisetteDiagnostic::error("Cannot use private method as a value")
2253        .with_infer_code("private_method_expression")
2254        .with_span_label(&span, "private methods must be called directly")
2255        .with_help("Use a closure instead: `|self_, args| self_.method(args)`")
2256}
2257
2258pub fn float_literal_int_cast(span: Span) -> LisetteDiagnostic {
2259    LisetteDiagnostic::error("Cannot cast float literal to integer directly")
2260        .with_infer_code("float_literal_int_cast")
2261        .with_span_label(&span, "unsupported cast")
2262        .with_help("Bind to a variable first: `let f = 1.0; f as int`")
2263}
2264
2265pub fn const_requires_simple_expression(span: Span) -> LisetteDiagnostic {
2266    LisetteDiagnostic::error("`const` requires a simple expression")
2267        .with_infer_code("const_requires_simple_expression")
2268        .with_span_label(&span, "expected literal or simple expression")
2269        .with_help("Use `let` for computed values")
2270}
2271
2272pub fn complex_sub_expression(span: Span) -> LisetteDiagnostic {
2273    LisetteDiagnostic::error("Complex expression used as sub-expression")
2274        .with_infer_code("complex_sub_expression")
2275        .with_span_label(&span, "expected simple expression")
2276        .with_help("Hoist to a `let` binding")
2277}
2278
2279pub fn reference_through_newtype(span: Span) -> LisetteDiagnostic {
2280    LisetteDiagnostic::error("Cannot take reference through newtype boundary")
2281        .with_infer_code("reference_through_newtype")
2282        .with_span_label(&span, "newtype `.0` inside `&`")
2283        .with_help("Bind the inner value first: `let inner = val.0; &inner`")
2284}
2285
2286pub fn immutable_argument_to_mut_param(
2287    var_name: &str,
2288    span: Span,
2289    is_external: bool,
2290) -> LisetteDiagnostic {
2291    let help = if is_external {
2292        format!(
2293            "Bindings in Lisette are immutable by default. Use `let mut {} = ...` to allow mutation",
2294            var_name
2295        )
2296    } else {
2297        format!(
2298            "Bindings in Lisette are immutable by default. Use `let mut {} = ...` to allow mutation, \
2299             or make the parameter immutable by removing `mut` from the function signature",
2300            var_name
2301        )
2302    };
2303    LisetteDiagnostic::error("Immutable argument passed to `mut` parameter")
2304        .with_infer_code("immutable_arg_to_mut_param")
2305        .with_span_label(&span, "expected mutable, found immutable")
2306        .with_help(help)
2307}
2308
2309pub fn failure_propagation_in_expression(span: Span) -> LisetteDiagnostic {
2310    LisetteDiagnostic::error("Failure propagation in expression position")
2311        .with_infer_code("failure_propagation_in_expression")
2312        .with_span_label(
2313            &span,
2314            "`Err(..)?` and `None?` always early-return and never produce a value",
2315        )
2316        .with_help("Use `return Err(..)` or `return None` instead")
2317}
2318
2319pub fn never_call_in_expression(span: Span) -> LisetteDiagnostic {
2320    LisetteDiagnostic::error("Never-returning call in expression position")
2321        .with_infer_code("never_call_in_expression")
2322        .with_span_label(&span, "`panic` never returns and cannot produce a value")
2323        .with_help("Use `panic(...)` as a statement instead")
2324}
2325
2326pub fn invalid_main_signature(span: Span) -> LisetteDiagnostic {
2327    LisetteDiagnostic::error("Invalid main signature")
2328        .with_infer_code("invalid_main_signature")
2329        .with_span_label(&span, "`main` must have no parameters and no return type")
2330        .with_help(
2331            "Use `fn main() { ... }`. To handle errors, use `match` or `if let` \
2332             inside main instead of returning `Result`.",
2333        )
2334}
2335
2336pub fn parenthesized_qualifier(path: &str, member: &str, span: Span) -> LisetteDiagnostic {
2337    LisetteDiagnostic::error("Unnecessary parentheses around qualifier")
2338        .with_infer_code("parenthesized_qualifier")
2339        .with_span_label(&span, "parenthesized qualifier")
2340        .with_help(format!("Remove the parentheses: `{}.{}`", path, member))
2341}
2342
2343pub fn type_alias_as_qualifier(
2344    alias: &str,
2345    underlying: &str,
2346    member: &str,
2347    span: Span,
2348) -> LisetteDiagnostic {
2349    LisetteDiagnostic::error("Cannot use generic type alias as qualifier")
2350        .with_infer_code("type_alias_as_qualifier")
2351        .with_span_label(
2352            &span,
2353            format!("`{}` aliases `{}`", alias, underlying),
2354        )
2355        .with_help(format!(
2356            "Aliases for types with generic parameters are not supported as qualifiers. Use the original type directly: `{}.{}`",
2357            underlying, member
2358        ))
2359}
2360
2361pub fn control_flow_in_expression(keyword: &str, span: Span) -> LisetteDiagnostic {
2362    LisetteDiagnostic::error(format!(
2363        "`{}` cannot be used in expression position",
2364        keyword
2365    ))
2366    .with_infer_code("control_flow_in_expression")
2367    .with_span_label(
2368        &span,
2369        format!("`{}` is a statement and cannot produce a value", keyword),
2370    )
2371    .with_help(format!(
2372        "Use `{}` as a standalone statement instead",
2373        keyword
2374    ))
2375}
2376
2377pub fn spread_on_non_variadic(span: Span) -> LisetteDiagnostic {
2378    LisetteDiagnostic::error("Invalid spread argument")
2379        .with_infer_code("spread_on_non_variadic")
2380        .with_span_label(&span, "this function does not accept variadic arguments")
2381        .with_help("Only functions with a `VarArgs<T>` parameter accept a spread argument")
2382}
2383
2384pub fn reference_aliases_sibling(ref_span: Span, var_name: &str) -> LisetteDiagnostic {
2385    LisetteDiagnostic::error("Reference aliases sibling expression")
2386        .with_infer_code("reference_aliases_sibling")
2387        .with_span_label(
2388            &ref_span,
2389            format!(
2390                "`&{}` could mutate `{}` used by a sibling",
2391                var_name, var_name
2392            ),
2393        )
2394        .with_help(format!(
2395            "Bind `{}` to a `let` before this expression to make evaluation order explicit",
2396            var_name
2397        ))
2398}