Skip to main content

lisette_diagnostics/
infer.rs

1use crate::LisetteDiagnostic;
2use syntax::ast::{Annotation, BinaryOperator, Span};
3use syntax::types::{SimpleKind, Type};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum MismatchedTailKind {
7    Result,
8    Option,
9    Partial,
10    Value,
11}
12
13impl MismatchedTailKind {
14    pub fn allow_alias(&self) -> &'static str {
15        match self {
16            Self::Result => "unused_result",
17            Self::Option => "unused_option",
18            Self::Partial => "unused_partial",
19            Self::Value => "unused_value",
20        }
21    }
22}
23
24pub fn mismatched_tail_value(
25    actual_span: &Span,
26    actual_ty: &str,
27    expected_span: &Span,
28    expected_ty: &str,
29) -> LisetteDiagnostic {
30    LisetteDiagnostic::error("Mismatch between return type and return value")
31        .with_infer_code("mismatched_return_value")
32        .with_span_primary_label(actual_span, format!("returns `{}`", actual_ty))
33        .with_span_label(
34            expected_span,
35            format!("has `{}` as implicit return type", expected_ty),
36        )
37        .with_help(format!(
38            "If the `{}` return type is intended, discard the return value with `let _ = ...`. If the `{}` return value is intended, add `-> {}` to the function signature.",
39            expected_ty, actual_ty, actual_ty
40        ))
41}
42
43pub fn blank_import_non_go(blank_span: Span) -> LisetteDiagnostic {
44    LisetteDiagnostic::error("Invalid import")
45        .with_resolve_code("blank_import_non_go")
46        .with_span_label(&blank_span, "only allowed for Go modules")
47        .with_help(
48            "Remove the underscore. Blank imports are allowed only for Go imports, \
49             because Lisette modules have no `init()` side effects.",
50        )
51}
52
53pub fn import_conflict(
54    alias: &str,
55    path1: &str,
56    path2: &str,
57    name_span: Span,
58) -> LisetteDiagnostic {
59    LisetteDiagnostic::error("Import conflict")
60        .with_resolve_code("import_conflict")
61        .with_span_label(
62            &name_span,
63            format!("conflicts with prior import `{}`", alias),
64        )
65        .with_help(format!(
66            "`{}` and `{}` resolve to the same name. Add an alias to at least one of them: \
67             `import my_{} \"{}\"`",
68            path1, path2, alias, path2
69        ))
70}
71
72pub fn reserved_import_alias(alias: &str, alias_span: Span) -> LisetteDiagnostic {
73    LisetteDiagnostic::error("Reserved import alias")
74        .with_resolve_code("reserved_import_alias")
75        .with_span_label(&alias_span, "reserved name")
76        .with_help(format!(
77            "`{}` is a reserved name and cannot be used as an import alias. \
78             Choose a different alias, e.g. `import my_{} \"...\"`",
79            alias, alias
80        ))
81}
82
83pub fn duplicate_import_path(path: &str, name_span: Span) -> LisetteDiagnostic {
84    LisetteDiagnostic::error("Duplicate import")
85        .with_resolve_code("duplicate_import")
86        .with_span_label(&name_span, "already imported above")
87        .with_help(format!(
88            "Module `{}` is already imported. Remove the duplicate import.",
89            path
90        ))
91}
92
93pub fn definition_shadows_import(
94    name: &str,
95    import_path: &str,
96    name_span: Span,
97) -> LisetteDiagnostic {
98    LisetteDiagnostic::error("Definition shadows import")
99        .with_resolve_code("definition_shadows_import")
100        .with_span_label(
101            &name_span,
102            format!("conflicts with imported module `{}`", import_path),
103        )
104        .with_help(format!(
105            "`{}` is already used as a module alias for `{}`. \
106             Rename this definition or use a different import alias.",
107            name, import_path
108        ))
109}
110
111pub fn statement_as_tail(span: Span) -> LisetteDiagnostic {
112    LisetteDiagnostic::error("Statement used as value")
113        .with_infer_code("statement_as_tail")
114        .with_span_label(&span, "this is a statement, not an expression")
115        .with_help(
116            "The last item in this block must be an expression that produces a value. \
117             Statements like `let`, `=`, `task`, and `defer` do not produce values.",
118        )
119}
120
121pub fn invalid_map_initialization(key: &Type, value: &Type, span: Span) -> LisetteDiagnostic {
122    LisetteDiagnostic::error("Invalid `Map` initialization")
123        .with_infer_code("invalid_map_initialization")
124        .with_span_label(&span, "invalid syntax")
125        .with_help(format!(
126            "To initialize a `Map`, use `Map.new<{}, {}>()`",
127            key, value
128        ))
129}
130
131pub fn self_type_not_supported(span: Span, impl_receiver: Option<&str>) -> LisetteDiagnostic {
132    let name_span = Span::new(span.file_id, span.byte_offset, 4); // "Self" is 4 chars
133    let help = match impl_receiver {
134        Some(name) => format!("Replace `Self` with `{}`.", name),
135        None => "Use a type parameter instead, e.g. `interface Comparable<T> { fn compare(self, other: T) -> int }`".to_string(),
136    };
137    LisetteDiagnostic::error("Use of `Self` type")
138        .with_resolve_code("self_type_not_supported")
139        .with_span_label(&name_span, "invalid type")
140        .with_help(help)
141}
142
143pub fn type_not_found(type_name: &str, annotation_span: Span) -> LisetteDiagnostic {
144    let simple_name = type_name.rsplit('.').next().unwrap_or(type_name);
145    let qualifier_offset = (type_name.len() - simple_name.len()) as u32;
146    let name_span = Span::new(
147        annotation_span.file_id,
148        annotation_span.byte_offset + qualifier_offset,
149        simple_name.len() as u32,
150    );
151
152    let looks_like_type_param = simple_name.len() == 1
153        && simple_name.chars().next().is_some_and(|c| c.is_uppercase())
154        || ["Key", "Value", "Item", "Error", "Elem", "In", "Out"].contains(&simple_name);
155
156    if looks_like_type_param {
157        return LisetteDiagnostic::error("Undeclared type parameter")
158            .with_resolve_code("type_not_found")
159            .with_span_label(&name_span, "undeclared")
160            .with_help(format!(
161                "Declare the type parameter, e.g. `impl<{t}>` or `fn foo<{t}>`",
162                t = simple_name
163            ));
164    }
165
166    LisetteDiagnostic::error("Type not found")
167        .with_resolve_code("type_not_found")
168        .with_span_label(&name_span, "type not found in scope")
169        .with_help("Define or import this type")
170}
171
172pub fn value_in_type_position(
173    name: &str,
174    kind: &str,
175    annotation_span: Span,
176    help: Option<String>,
177) -> LisetteDiagnostic {
178    let name_span = Span::new(
179        annotation_span.file_id,
180        annotation_span.byte_offset,
181        name.len() as u32,
182    );
183
184    let mut diag = LisetteDiagnostic::error("Value in type position")
185        .with_resolve_code("value_in_type_position")
186        .with_span_label(&name_span, format!("expected type, found {}", kind));
187
188    if let Some(help) = help {
189        diag = diag.with_help(help);
190    }
191
192    diag
193}
194
195pub fn undeclared_impl_type_param(
196    type_name: &str,
197    annotation_span: Span,
198    receiver_name: &str,
199) -> LisetteDiagnostic {
200    let name_span = Span::new(
201        annotation_span.file_id,
202        annotation_span.byte_offset,
203        type_name.len() as u32,
204    );
205
206    LisetteDiagnostic::error("Undeclared type parameter")
207        .with_resolve_code("type_not_found")
208        .with_span_label(&name_span, "undeclared")
209        .with_help(format!(
210            "Declare the type parameter: `impl<{t}> {r}<{t}>`",
211            t = type_name,
212            r = receiver_name
213        ))
214}
215
216pub fn type_param_with_args(type_arg_count: usize, span: Span) -> LisetteDiagnostic {
217    let noun = if type_arg_count == 1 {
218        "type argument"
219    } else {
220        "type arguments"
221    };
222
223    LisetteDiagnostic::error("Invalid type argument")
224        .with_infer_code("type_param_with_args")
225        .with_span_label(&span, "type is not parameterized")
226        .with_help(format!("Remove {}", noun))
227}
228
229pub fn type_args_on_non_generic(type_arg_count: usize, span: Span) -> LisetteDiagnostic {
230    let noun = if type_arg_count == 1 {
231        "type argument"
232    } else {
233        "type arguments"
234    };
235
236    LisetteDiagnostic::error("Unexpected type arguments")
237        .with_infer_code("type_arg_on_non_generic")
238        .with_span_label(&span, "accepts no type arguments")
239        .with_help(format!("Remove the {} from this call", noun))
240}
241
242pub fn circular_type_alias(type_name: &str, span: Span) -> LisetteDiagnostic {
243    LisetteDiagnostic::error("Circular type alias")
244        .with_resolve_code("circular_type_alias")
245        .with_span_label(&span, format!("`{}` references itself", type_name))
246        .with_help("Type aliases cannot be recursive")
247}
248
249pub fn const_disallows_composite(span: Span) -> LisetteDiagnostic {
250    LisetteDiagnostic::error("Composite value in `const`")
251        .with_infer_code("const_disallows_composite")
252        .with_span_label(&span, "not allowed")
253        .with_help("`const` only accepts primitive values: `bool`, `int`, `float`, and `string`")
254}
255
256pub fn const_cycle(cycle: &[String], span: Span) -> LisetteDiagnostic {
257    let mut diagnostic = LisetteDiagnostic::error("`const` init cycle")
258        .with_infer_code("const_cycle")
259        .with_help(
260            "`const` initializers cannot refer to themselves, either directly or transitively",
261        );
262    diagnostic = if cycle.len() == 1 {
263        diagnostic.with_span_label(&span, "self-reference")
264    } else {
265        let chain = cycle
266            .iter()
267            .map(|name| format!("`{}`", name))
268            .collect::<Vec<_>>()
269            .join(" → ");
270        diagnostic.with_span_label(&span, format!("cycle: {} → `{}`", chain, cycle[0]))
271    };
272    diagnostic
273}
274
275pub fn name_not_found(
276    variable_name: &str,
277    span: Span,
278    available_names: &[String],
279    expected_ty: Option<&Type>,
280) -> LisetteDiagnostic {
281    if matches!(variable_name, "nil" | "null" | "Nil" | "undefined") {
282        let help = nil_help_for(expected_ty);
283        return LisetteDiagnostic::error(format!("`{}` is not supported", variable_name))
284            .with_resolve_code("nil_not_supported")
285            .with_span_label(&span, "does not exist")
286            .with_help(help);
287    }
288
289    if let Some(hint) = go_builtin_hint(variable_name) {
290        return LisetteDiagnostic::error("Name not found")
291            .with_resolve_code("name_not_found")
292            .with_span_label(&span, "name not found in scope")
293            .with_help(hint);
294    }
295
296    let mut diagnostic = LisetteDiagnostic::error("Name not found")
297        .with_resolve_code("name_not_found")
298        .with_span_label(&span, "name not found in scope");
299
300    let suggestion = available_names
301        .iter()
302        .filter_map(|c| {
303            let d = levenshtein_distance(variable_name, c);
304            (d <= 2).then_some((c, d))
305        })
306        .min_by_key(|(_, d)| *d)
307        .map(|(c, _)| c.clone());
308
309    if let Some(suggestion) = suggestion {
310        diagnostic = diagnostic.with_help(format!("Did you mean `{}`?", suggestion));
311    } else {
312        diagnostic = diagnostic.with_help(format!("Define or import `{}`.", variable_name));
313    }
314
315    diagnostic
316}
317
318/// Pick a `nil`-replacement hint tailored to the expected type.
319fn nil_help_for(expected_ty: Option<&Type>) -> String {
320    match expected_ty {
321        Some(ty) if ty.is_slice() => format!("For an empty `{}`, use `[]`.", ty),
322        Some(ty) if ty.is_map() => format!("For an empty `{}`, use `Map.new()`.", ty),
323        _ => {
324            "Absence is encoded with `Option<T>` in Lisette. Use `None` to represent absent values."
325                .to_string()
326        }
327    }
328}
329
330pub fn self_in_static_method(span: Span) -> LisetteDiagnostic {
331    LisetteDiagnostic::error("Invalid `self`")
332        .with_resolve_code("self_in_static_method")
333        .with_span_label(&span, "`self` is not available here")
334        .with_help("Add a `self` parameter to the method if you need an instance method")
335}
336
337pub fn static_method_called_on_instance(
338    method_name: &str,
339    type_name: &str,
340    span: Span,
341) -> LisetteDiagnostic {
342    LisetteDiagnostic::error("Static method called on instance")
343        .with_infer_code("static_method_on_instance")
344        .with_span_label(&span, format!("`{}` is a static method", method_name))
345        .with_help(format!(
346            "Call it as `{}.{}(...)` on the type, not on an instance",
347            type_name, method_name
348        ))
349}
350
351pub fn function_or_value_not_found_in_module(name: &str, span: Span) -> LisetteDiagnostic {
352    LisetteDiagnostic::error("Name not found")
353        .with_resolve_code("not_found_in_module")
354        .with_span_label(&span, format!("`{}` not found in module", name))
355        .with_help("Ensure the name is exported and spelled correctly")
356}
357
358pub fn receiver_type_mismatch(
359    impl_type: &str,
360    receiver_type: &str,
361    span: Span,
362) -> LisetteDiagnostic {
363    LisetteDiagnostic::error("Type mismatch")
364        .with_infer_code("receiver_type_mismatch")
365        .with_span_label(
366            &span,
367            format!(
368                "expected `{}` or `Ref<{}>`, found `{}`",
369                impl_type, impl_type, receiver_type
370            ),
371        )
372        .with_help(format!(
373            "Change the receiver type to `{}` or `Ref<{}>`",
374            impl_type, impl_type
375        ))
376}
377
378pub fn receiver_must_be_named_self(actual_name: &str, span: Span) -> LisetteDiagnostic {
379    LisetteDiagnostic::error("Invalid receiver name")
380        .with_infer_code("receiver_not_self")
381        .with_span_label(&span, "expected `self`")
382        .with_help(format!(
383            "Rename `{}` to `self`. In an instance method definition, Lisette expects the first parameter to be named `self`",
384            actual_name
385        ))
386}
387
388pub fn stringer_signature_mismatch(method_name: &str, span: Span) -> LisetteDiagnostic {
389    LisetteDiagnostic::error("Reserved method signature")
390        .with_infer_code("stringer_signature_mismatch")
391        .with_span_label(
392            &span,
393            format!("`{}` must have signature `(self) -> string`", method_name),
394        )
395        .with_help(format!(
396            "`{}` 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",
397            method_name
398        ))
399}
400
401pub fn json_method_override(method_name: &str, span: Span) -> LisetteDiagnostic {
402    LisetteDiagnostic::error("Reserved JSON method")
403        .with_infer_code("json_method_override")
404        .with_span_label(
405            &span,
406            format!("`{}` collides with the method generated by `#[json]`", method_name),
407        )
408        .with_help(
409            "`#[json]` already generates `MarshalJSON` and `UnmarshalJSON`. Remove this method, or drop `#[json]` and write both yourself",
410        )
411}
412
413pub fn json_non_serializable_field(span: &Span, kind: &str, skippable: bool) -> LisetteDiagnostic {
414    let fix = if skippable {
415        " Drop the field, or exclude it with `#[json(skip)]`."
416    } else {
417        " Remove it from the variant."
418    };
419    LisetteDiagnostic::error("Non-serializable field in a `#[json]` type")
420        .with_infer_code("json_non_serializable_field")
421        .with_span_label(span, format!("a {kind} cannot be JSON-encoded"))
422        .with_help(format!("Go's `encoding/json` cannot marshal {kind}s.{fix}"))
423}
424
425pub fn disallowed_mutation(
426    variable_name: &str,
427    span: Span,
428    self_type_name: Option<&str>,
429    is_pattern_binding: bool,
430    is_const_binding: bool,
431) -> LisetteDiagnostic {
432    if variable_name == "self" {
433        if let Some(type_name) = self_type_name {
434            LisetteDiagnostic::error("Immutable receiver")
435                .with_infer_code("value_receiver_immutable")
436                .with_span_label(&span, "receiver is immutable")
437                .with_help(format!(
438                    "Use `self: Ref<{type_name}>` to make the receiver mutable"
439                ))
440        } else {
441            LisetteDiagnostic::error("Immutable receiver")
442                .with_infer_code("value_receiver_immutable")
443                .with_span_label(&span, "receiver is immutable")
444                .with_help("Use `self: Ref<Self>` to make the receiver mutable")
445        }
446    } else if is_const_binding {
447        LisetteDiagnostic::error("Cannot mutate `const`")
448            .with_infer_code("immutable")
449            .with_span_label(&span, "cannot mutate a `const`")
450            .with_help(format!(
451                "`const` bindings are immutable. Rebind with `let mut {variable_name} = {variable_name}` to mutate a local copy"
452            ))
453    } else if is_pattern_binding {
454        LisetteDiagnostic::error("Immutable variable")
455            .with_infer_code("immutable")
456            .with_span_label(&span, "cannot mutate an immutable variable")
457            .with_help(format!(
458                "Pattern bindings are immutable; rebind with `let mut {variable_name} = {variable_name}` to mutate"
459            ))
460    } else {
461        LisetteDiagnostic::error("Immutable variable")
462            .with_infer_code("immutable")
463            .with_span_label(&span, "cannot mutate an immutable variable")
464            .with_help(format!(
465                "Declare using `let mut {variable_name}` to make the variable mutable"
466            ))
467    }
468}
469
470pub fn self_reference_in_assignment(span: Span) -> LisetteDiagnostic {
471    LisetteDiagnostic::error("Cannot reassign variable while taking its reference")
472        .with_infer_code("self_reference_in_assignment")
473        .with_span_label(&span, "disallowed")
474        .with_help("Separate the reassignment from reference taking, or use a different variable")
475}
476
477pub fn uppercase_binding(span: Span, name: &str) -> LisetteDiagnostic {
478    LisetteDiagnostic::error("Invalid binding name")
479        .with_infer_code("uppercase_binding")
480        .with_span_label(&span, "binding names must start with a lowercase letter")
481        .with_help(format!("Use a lowercase name instead of `{}`", name))
482}
483
484pub fn enum_variant_constructor_not_found(
485    span: Span,
486    enum_info: Option<(&str, &[String])>,
487    variant_name: &str,
488    bare_allowed: bool,
489) -> LisetteDiagnostic {
490    let help = if let Some((enum_name, variants)) = enum_info {
491        if variants.iter().any(|v| v == variant_name) {
492            if bare_allowed {
493                format!("Use `{}` to match this variant", variant_name)
494            } else {
495                format!("Use `{}.{}` to match this variant", enum_name, variant_name)
496            }
497        } else if let Some(closest) = variants
498            .iter()
499            .filter_map(|v| {
500                let d = levenshtein_distance(variant_name, v);
501                (d <= 2).then_some((v, d))
502            })
503            .min_by_key(|(_, d)| *d)
504            .map(|(v, _)| v)
505        {
506            if bare_allowed {
507                format!("Did you mean `{}`?", closest)
508            } else {
509                format!("Did you mean `{}.{}`?", enum_name, closest)
510            }
511        } else {
512            let variants_fmt = if bare_allowed {
513                format_list(variants, |v| format!("`{}`", v))
514            } else {
515                format_list(variants, |v| format!("`{}.{}`", enum_name, v))
516            };
517            format!(
518                "Available variants for `{}` are {}",
519                enum_name, variants_fmt
520            )
521        }
522    } else {
523        "Check that the variant is defined in the enum and spelled correctly".to_string()
524    };
525
526    LisetteDiagnostic::error("Variant not found")
527        .with_resolve_code("variant_not_found")
528        .with_span_label(&span, "not found")
529        .with_help(help)
530}
531
532pub fn const_pattern_not_eligible(name: &str, span: Span) -> LisetteDiagnostic {
533    LisetteDiagnostic::error("Pattern target is not a matchable value")
534        .with_infer_code("const_pattern_not_eligible")
535        .with_span_label(&span, "a function cannot be a match pattern")
536        .with_help(format!(
537            "`{}` is a function or method value. Const patterns match named constants or package-level values that the compiler can emit as a Go `case`, not callables.",
538            name
539        ))
540}
541
542pub fn const_pattern_outside_match_arm(name: &str, span: Span) -> LisetteDiagnostic {
543    LisetteDiagnostic::error("Const pattern outside a match arm")
544        .with_infer_code("const_pattern_outside_match_arm")
545        .with_span_label(&span, "const patterns are only allowed in match arms")
546        .with_help(format!(
547            "`{}` is a constant, so this pattern is refutable. Match on it inside a `match` expression, or compare with `==` in a `let` or function parameter.",
548            name
549        ))
550}
551
552pub fn arity_mismatch(
553    expected: &[Type],
554    actual: &[Type],
555    generic_params: &[String],
556    is_constructor: bool,
557    span: Span,
558) -> LisetteDiagnostic {
559    let expected_str = if !generic_params.is_empty() {
560        generic_params.join(", ")
561    } else {
562        expected
563            .iter()
564            .map(|t| t.to_string())
565            .collect::<Vec<_>>()
566            .join(", ")
567    };
568
569    let actual_str = actual
570        .iter()
571        .map(|t| t.to_string())
572        .collect::<Vec<_>>()
573        .join(", ");
574
575    let expected_count = expected.len();
576    let actual_count = actual.len();
577    let expected_word = if expected_count == 1 {
578        "argument"
579    } else {
580        "arguments"
581    };
582    let actual_word = if actual_count == 1 {
583        "argument"
584    } else {
585        "arguments"
586    };
587
588    LisetteDiagnostic::error("Wrong argument count")
589        .with_infer_code("arg_count_mismatch")
590        .with_span_label(
591            &span,
592            format!("expected `({})`, found `({})`", expected_str, actual_str),
593        )
594        .with_help(format!(
595            "This {} expects {} {} but received {} {}",
596            if is_constructor {
597                "constructor"
598            } else {
599                "function"
600            },
601            expected_count,
602            expected_word,
603            actual_count,
604            actual_word
605        ))
606}
607
608pub fn generics_arity_mismatch(
609    expected_generic_params: &[String],
610    actual_type_args: &[Annotation],
611    actual_types: &[Type],
612    span: Span,
613) -> LisetteDiagnostic {
614    let expected: Vec<Type> = expected_generic_params
615        .iter()
616        .map(|param| Type::Parameter(param.as_str().into()))
617        .collect();
618
619    let expected_str = expected
620        .iter()
621        .map(|t| t.to_string())
622        .collect::<Vec<_>>()
623        .join(", ");
624
625    let actual_str = actual_types
626        .iter()
627        .map(|t| t.to_string())
628        .collect::<Vec<_>>()
629        .join(", ");
630
631    let expected_count = expected.len();
632    let actual_count = actual_types.len();
633    let expected_word = if expected_count == 1 {
634        "type parameter"
635    } else {
636        "type parameters"
637    };
638    let actual_word = if actual_count == 1 {
639        "type parameter"
640    } else {
641        "type parameters"
642    };
643
644    let generics_span =
645        if let (Some(first), Some(last)) = (actual_type_args.first(), actual_type_args.last()) {
646            let first_span = first.get_span();
647            let last_span = last.get_span();
648            Span::new(
649                first_span.file_id,
650                first_span.byte_offset.saturating_sub(1),
651                (last_span.byte_offset + last_span.byte_length + 1)
652                    .saturating_sub(first_span.byte_offset.saturating_sub(1)),
653            )
654        } else {
655            span
656        };
657
658    LisetteDiagnostic::error("Wrong type argument count")
659        .with_infer_code("type_arg_count_mismatch")
660        .with_span_label(
661            &generics_span,
662            format!("expected `<{}>`, found `<{}>`", expected_str, actual_str),
663        )
664        .with_help(format!(
665            "This type expects {} {} but received {} {}",
666            expected_count, expected_word, actual_count, actual_word
667        ))
668}
669
670pub fn tuple_arity_mismatch(
671    pattern_arity: usize,
672    expected_arity: usize,
673    span: Span,
674) -> LisetteDiagnostic {
675    let expected_word = if expected_arity == 1 {
676        "element"
677    } else {
678        "elements"
679    };
680    let actual_word = if pattern_arity == 1 {
681        "element"
682    } else {
683        "elements"
684    };
685    LisetteDiagnostic::error("Tuple arity mismatch")
686        .with_infer_code("tuple_element_count_mismatch")
687        .with_span_label(
688            &span,
689            format!(
690                "expected {} {}, found {} {}",
691                expected_arity, expected_word, pattern_arity, actual_word
692            ),
693        )
694        .with_help("Adjust the pattern to match the number of elements in the tuple.")
695}
696
697pub fn struct_not_found(identifier: &str, span: Span) -> LisetteDiagnostic {
698    let simple_name = identifier.rsplit('.').next().unwrap_or(identifier);
699    let qualifier_offset = (identifier.len() - simple_name.len()) as u32;
700    let name_span = Span::new(
701        span.file_id,
702        span.byte_offset + qualifier_offset,
703        simple_name.len() as u32,
704    );
705
706    LisetteDiagnostic::error("Struct not found")
707        .with_resolve_code("struct_not_found")
708        .with_span_label(&name_span, "struct not found in scope")
709        .with_help("Define or import this struct")
710}
711
712pub fn struct_missing_fields(
713    struct_name: &str,
714    missing: &[String],
715    span: Span,
716) -> LisetteDiagnostic {
717    let fields_list = missing.join(", ");
718
719    let simple_name = struct_name.rsplit('.').next().unwrap_or(struct_name);
720    let qualifier_offset = (struct_name.len() - simple_name.len()) as u32;
721    let name_span = Span::new(
722        span.file_id,
723        span.byte_offset + qualifier_offset,
724        simple_name.len() as u32,
725    );
726
727    LisetteDiagnostic::error(format!("Struct `{}` is missing fields", simple_name))
728        .with_infer_code("missing_struct_fields")
729        .with_span_label(&name_span, format!("missing fields: {}", fields_list))
730        .with_help("Initialize all fields, or add `..` to zero-fill the rest")
731}
732
733pub fn pattern_missing_fields(missing: &[String], span: Span) -> LisetteDiagnostic {
734    let (noun, fields_fmt) = if missing.len() == 1 {
735        ("field", format!("`{}`", missing[0]))
736    } else {
737        let formatted: Vec<String> = missing.iter().map(|f| format!("`{}`", f)).collect();
738        ("fields", formatted.join(", "))
739    };
740
741    let pronoun = if missing.len() == 1 { "it" } else { "them" };
742
743    LisetteDiagnostic::error("Missing pattern fields")
744        .with_infer_code("pattern_missing_fields")
745        .with_span_label(&span, format!("missing {}", fields_fmt))
746        .with_help(format!(
747            "Include the missing {}, or use `..` to ignore {}",
748            noun, pronoun
749        ))
750}
751
752pub fn private_field_access(field_name: &str, struct_name: &str, span: Span) -> LisetteDiagnostic {
753    LisetteDiagnostic::error("Private field")
754        .with_resolve_code("private_field_access")
755        .with_span_label(&span, "private")
756        .with_help(format!(
757            "Cannot access private field `{}` of struct `{}`. Mark the field as `pub`.",
758            field_name, struct_name
759        ))
760}
761
762pub fn private_method_access(method_name: &str, type_name: &str, span: Span) -> LisetteDiagnostic {
763    LisetteDiagnostic::error("Private method")
764        .with_resolve_code("private_method_access")
765        .with_span_label(&span, "private")
766        .with_help(format!(
767            "Cannot access private method `{}` of type `{}`. Mark the method as `pub`.",
768            method_name, type_name
769        ))
770}
771
772pub fn private_field_in_spread(
773    field_name: &str,
774    struct_name: &str,
775    span: Span,
776) -> LisetteDiagnostic {
777    LisetteDiagnostic::error("Private field")
778        .with_resolve_code("private_field_spread")
779        .with_span_label(&span, "private")
780        .with_help(format!(
781            "Cannot spread `{}` because field `{}` is private. Mark the field as `pub`.",
782            struct_name, field_name
783        ))
784}
785
786pub fn private_field_in_zero_fill(
787    field_name: &str,
788    struct_name: &str,
789    owning_module: &str,
790    span: Span,
791) -> LisetteDiagnostic {
792    LisetteDiagnostic::error("Private field")
793        .with_resolve_code("private_field_zero_fill")
794        .with_span_label(&span, "private")
795        .with_help(format!(
796            "`{}` of `{}` cannot be zero-filled because `{}` is private to module `{}`. \
797             Provide an explicit value, or have `{}` expose `{}` as `pub` or offer a \
798             constructor.",
799            field_name, struct_name, field_name, owning_module, owning_module, field_name
800        ))
801}
802
803pub fn field_no_zero(
804    struct_name: &str,
805    field_name: &str,
806    field_ty: &Type,
807    chain: &[&str],
808    private: Option<(&str, &str, &str)>,
809    span: Span,
810) -> LisetteDiagnostic {
811    let main = match private {
812        Some((priv_struct, priv_field, priv_module)) => format!(
813            "`{}` of `{}` cannot be zero-filled because `{}.{}` is private to module `{}`. \
814             Provide an explicit value for `{}`, or have `{}` expose `{}` as `pub`.",
815            field_name,
816            struct_name,
817            priv_struct,
818            priv_field,
819            priv_module,
820            field_name,
821            priv_module,
822            priv_field
823        ),
824        None if chain.is_empty() => format!(
825            "Field `{}` of type `{}` has no zero value. Provide an explicit value, \
826             or wrap the field type in `Option<T>`.",
827            field_name, field_ty
828        ),
829        None => format!(
830            "Field `{}.{}` of type `{}` has no zero value. Provide an explicit value for \
831             `{}`, or wrap the field type in `Option<T>`.",
832            field_name,
833            chain.join("."),
834            field_ty,
835            field_name
836        ),
837    };
838    LisetteDiagnostic::error("Field has no zero value")
839        .with_infer_code("field_no_zero")
840        .with_span_label(&span, "no zero available")
841        .with_help(main)
842}
843
844pub fn unresolved_receiver_type(member: &str, span: Span) -> LisetteDiagnostic {
845    LisetteDiagnostic::error("Cannot infer receiver type")
846        .with_infer_code("unresolved_receiver_type")
847        .with_span_label(
848            &span,
849            format!(
850                "cannot resolve `.{}` because the receiver type is unknown",
851                member
852            ),
853        )
854        .with_help(
855            "Annotate the receiver's binding, e.g. `let x: SomeType = ...` or \
856             `|param: SomeType| ...`.",
857        )
858}
859
860pub fn member_not_found(
861    ty: &Type,
862    field: &str,
863    span: Span,
864    available_fields: Option<&[String]>,
865    unwrap_hint: Option<UnwrapHint>,
866    is_call_target: bool,
867) -> LisetteDiagnostic {
868    let mut diagnostic = LisetteDiagnostic::error("Member not found")
869        .with_infer_code("member_not_found")
870        .with_span_label(&span, format!("no member `{}` on type `{}`", field, ty));
871
872    if matches!(field, "unwrap" | "expect") && (ty.is_option() || ty.is_result() || ty.is_partial())
873    {
874        let help = if ty.is_option() {
875            format!(
876                "Lisette does not provide `{}()`. Use `?` to propagate, `match` to handle both \
877                 cases (e.g. `match <expr> {{ Some(x) => x, None => ... }}`), `let else` for \
878                 early exit, or `unwrap_or(default)` for a fallback.",
879                field
880            )
881        } else if ty.is_result() {
882            format!(
883                "Lisette does not provide `{}()`. Use `?` to propagate, `match` to handle both \
884                 cases (e.g. `match <expr> {{ Ok(x) => x, Err(e) => ... }}`), `let else` for \
885                 early exit, or `unwrap_or(default)` for a fallback.",
886                field
887            )
888        } else {
889            format!(
890                "Lisette does not provide `{}()`. The `?` operator is not supported on \
891                 `Partial`; use `match` to handle all three cases (e.g. `match <expr> \
892                 {{ Ok(x) => ..., Err(e) => ..., Both(x, e) => ... }}`) or `unwrap_or(default)` \
893                 for a fallback.",
894                field
895            )
896        };
897        diagnostic = diagnostic.with_help(help);
898        return diagnostic;
899    }
900
901    if let Some(hint) = unwrap_hint {
902        let (wrapper_name, pattern) = match hint.wrapper {
903            UnwrapWrapper::Option => (
904                "Option",
905                format!(
906                    "match <expr> {{ Some(x) => x.{}(...), None => ... }}",
907                    field
908                ),
909            ),
910            UnwrapWrapper::Result => (
911                "Result",
912                format!(
913                    "match <expr> {{ Ok(x) => x.{}(...), Err(e) => ... }}",
914                    field
915                ),
916            ),
917        };
918        diagnostic = diagnostic.with_help(format!(
919            "Unwrap the `{}` to extract the `{}` value, then call `{}` on it, e.g. `{}`",
920            wrapper_name, hint.inner_ty, field, pattern
921        ));
922        return diagnostic;
923    }
924
925    let suggestion = available_fields.and_then(|fields| find_similar_name(field, fields));
926
927    if let Some(suggestion) = suggestion {
928        let rendered = if is_call_target {
929            format!("{}()", suggestion)
930        } else {
931            suggestion
932        };
933        diagnostic = diagnostic.with_help(format!("Did you mean `{}`?", rendered));
934    } else {
935        diagnostic = diagnostic.with_help("Ensure the field or method is defined on this type");
936    }
937
938    diagnostic
939}
940
941#[derive(Debug, Clone, Copy)]
942pub enum UnwrapWrapper {
943    Option,
944    Result,
945}
946
947#[derive(Debug, Clone)]
948pub struct UnwrapHint {
949    pub wrapper: UnwrapWrapper,
950    pub inner_ty: Type,
951}
952
953pub fn not_numeric(ty: &Type, span: Span) -> LisetteDiagnostic {
954    LisetteDiagnostic::error("Type mismatch")
955        .with_infer_code("type_mismatch")
956        .with_span_label(&span, format!("expected `int` or `float`, found `{}`", ty))
957        .with_help("The negation operator `-` can only be used with `int` or `float`")
958}
959
960pub fn not_integer(ty: &Type, span: Span) -> LisetteDiagnostic {
961    LisetteDiagnostic::error("Type mismatch")
962        .with_infer_code("type_mismatch")
963        .with_span_label(&span, format!("expected integer type, found `{}`", ty))
964        .with_help("The bitwise complement operator `^` can only be used with integer types")
965}
966
967pub fn not_numeric_for_binary(
968    operator: &BinaryOperator,
969    ty: &Type,
970    span: Span,
971) -> LisetteDiagnostic {
972    LisetteDiagnostic::error("Type mismatch")
973        .with_infer_code("type_mismatch")
974        .with_span_label(&span, format!("expected `int` or `float`, found `{}`", ty))
975        .with_help(format!(
976            "The `{}` operator can only be used with `int` or `float`",
977            operator
978        ))
979}
980
981pub fn not_integer_for_binary(
982    operator: &BinaryOperator,
983    ty: &Type,
984    span: Span,
985) -> LisetteDiagnostic {
986    LisetteDiagnostic::error("Type mismatch")
987        .with_infer_code("type_mismatch")
988        .with_span_label(&span, format!("expected integer type, found `{}`", ty))
989        .with_help(format!(
990            "The `{}` operator can only be used with integer types",
991            operator
992        ))
993}
994
995pub fn binary_operator_type_mismatch(
996    operator: &BinaryOperator,
997    left_ty: &Type,
998    right_ty: &Type,
999    span: Span,
1000) -> LisetteDiagnostic {
1001    let label_msg = format!(
1002        "cannot {} `{}` and `{}`",
1003        operator_verb(operator),
1004        left_ty,
1005        right_ty
1006    );
1007
1008    LisetteDiagnostic::error("Type mismatch")
1009        .with_infer_code("type_mismatch")
1010        .with_span_label(&span, label_msg)
1011        .with_help(format!(
1012            "The `{}` operator {}",
1013            operator,
1014            operator_help(operator)
1015        ))
1016}
1017
1018pub fn not_orderable(ty: &Type, span: Span) -> LisetteDiagnostic {
1019    LisetteDiagnostic::error("Type mismatch")
1020        .with_infer_code("type_mismatch")
1021        .with_span_label(&span, format!("expected orderable, found `{}`", ty))
1022        .with_help("Use comparison operators only with numeric, string, or boolean types")
1023}
1024
1025pub fn not_comparable(ty: &Type, reason: &str, span: Span) -> LisetteDiagnostic {
1026    LisetteDiagnostic::error("Type mismatch")
1027        .with_infer_code("type_mismatch")
1028        .with_span_label(&span, format!("`{}` cannot be compared with `==`", ty))
1029        .with_help(format!(
1030            "The `==` and `!=` operators cannot be used on {} because they are not comparable in Go",
1031            reason
1032        ))
1033}
1034
1035pub fn not_orderable_bound(span: Span) -> LisetteDiagnostic {
1036    LisetteDiagnostic::error("Bound not satisfied")
1037        .with_infer_code("not_orderable_bound")
1038        .with_span_label(&span, "does not satisfy `cmp.Ordered`")
1039        .with_help(
1040            "The type parameter must be `cmp.Ordered` but the argument is not orderable. \
1041             Relax the bound or pass an argument that satisfies it",
1042        )
1043}
1044
1045pub fn not_comparable_bound(span: Span) -> LisetteDiagnostic {
1046    LisetteDiagnostic::error("Bound not satisfied")
1047        .with_infer_code("not_comparable_bound")
1048        .with_span_label(&span, "does not satisfy `Comparable`")
1049        .with_help(
1050            "The parameter must be `Comparable` but the argument is not comparable. \
1051             Relax the bound or pass an argument that satisfies it",
1052        )
1053}
1054
1055pub fn bound_only_in_value_position(name: &str, span: Span) -> LisetteDiagnostic {
1056    LisetteDiagnostic::error(format!("`{}` is a bound, not a value type", name))
1057        .with_infer_code("bound_only_in_value_position")
1058        .with_span_label(&span, "not allowed here")
1059        .with_help(format!(
1060            "Use `{}` only as a bound to constrain a generic parameter, e.g. `fn f<T: {}>(x: T)`",
1061            name, name
1062        ))
1063}
1064
1065pub fn missing_bound_on_param(
1066    param_name: &str,
1067    required_bound: &str,
1068    span: Span,
1069) -> LisetteDiagnostic {
1070    let short = required_bound.rsplit('.').next().unwrap_or(required_bound);
1071    LisetteDiagnostic::error("Missing bound on type parameter")
1072        .with_infer_code("missing_bound_on_param")
1073        .with_span_label(&span, format!("does not satisfy `{}`", short))
1074        .with_help(format!(
1075            "The parameter must be `{}` but the argument is unbounded. \
1076             Add this bound to the enclosing function: `<{}: {}>`",
1077            required_bound, param_name, required_bound
1078        ))
1079}
1080
1081pub fn division_by_zero(span: Span) -> LisetteDiagnostic {
1082    LisetteDiagnostic::error("Division by zero")
1083        .with_infer_code("division_by_zero")
1084        .with_span_label(&span, "cannot divide by zero")
1085        .with_help("This operation will panic at runtime")
1086}
1087
1088pub fn incompatible_named_types(underlying_ty: &Type, span: Span) -> LisetteDiagnostic {
1089    LisetteDiagnostic::error("Type mismatch")
1090        .with_infer_code("incompatible_named_types")
1091        .with_span_label(&span, "cannot compute")
1092        .with_help(format!(
1093            "Cast one to the other's type, or convert both to `{}`",
1094            underlying_ty
1095        ))
1096}
1097
1098pub fn named_primitive_needs_cast(
1099    primitive_ty: &Type,
1100    named_ty: &Type,
1101    span: Span,
1102) -> LisetteDiagnostic {
1103    LisetteDiagnostic::error("Type mismatch")
1104        .with_infer_code("type_mismatch")
1105        .with_span_label(
1106            &span,
1107            format!("cannot compute `{}` with `{}`", primitive_ty, named_ty),
1108        )
1109        .with_help(format!("Cast with `as`, e.g. `value as {}`", named_ty))
1110}
1111
1112pub fn invalid_division_order(
1113    operator: &BinaryOperator,
1114    left_ty: &Type,
1115    right_ty: &Type,
1116    span: Span,
1117) -> LisetteDiagnostic {
1118    let (op_symbol, help_msg) = match operator {
1119        BinaryOperator::Division => (
1120            "/",
1121            format!(
1122                "To divide by `{}`, the dividend (left operand) must also be `{}`",
1123                right_ty, right_ty
1124            ),
1125        ),
1126        BinaryOperator::Remainder => (
1127            "%",
1128            format!(
1129                "To take the remainder by `{}`, the dividend (left operand) must also be `{}`",
1130                right_ty, right_ty
1131            ),
1132        ),
1133        _ => unreachable!(),
1134    };
1135
1136    LisetteDiagnostic::error("Invalid operation")
1137        .with_infer_code("invalid_division_order")
1138        .with_span_label(
1139            &span,
1140            format!("cannot compute `{}` {} `{}`", left_ty, op_symbol, right_ty),
1141        )
1142        .with_help(help_msg)
1143}
1144
1145pub fn branch_type_mismatch(
1146    consequence_ty: &Type,
1147    consequence_span: Span,
1148    alternative_ty: &Type,
1149    alternative_span: Span,
1150) -> LisetteDiagnostic {
1151    LisetteDiagnostic::error("Type mismatch")
1152        .with_infer_code("type_mismatch")
1153        .with_span_label(
1154            &consequence_span,
1155            format!("this branch returns `{}`", consequence_ty),
1156        )
1157        .with_span_label(
1158            &alternative_span,
1159            format!("this branch returns `{}`", alternative_ty),
1160        )
1161        .with_help("All branches must return the same type")
1162}
1163
1164pub fn let_else_must_diverge(span: Span) -> LisetteDiagnostic {
1165    LisetteDiagnostic::error("Invalid `else` block")
1166        .with_infer_code("let_else_must_diverge")
1167        .with_span_primary_label(&span, "this branch does not diverge")
1168        .with_help("Add `return`, `break`, `continue`, or a diverging call in the `else` block")
1169}
1170
1171pub fn return_outside_function(span: Span) -> LisetteDiagnostic {
1172    LisetteDiagnostic::error("`return` outside function")
1173        .with_infer_code("return_outside_function")
1174        .with_span_label(&span, "`return` outside function")
1175        .with_help("Use `return` only inside a function body")
1176}
1177
1178pub fn disallowed_mut_use(span: Span) -> LisetteDiagnostic {
1179    LisetteDiagnostic::error("Invalid `mut`")
1180        .with_infer_code("mut_not_allowed")
1181        .with_span_label(&span, "not allowed here")
1182        .with_help("`mut` is not allowed with destructuring patterns")
1183}
1184
1185pub fn cannot_match_on_functions(span: Span) -> LisetteDiagnostic {
1186    LisetteDiagnostic::error("Invalid pattern")
1187        .with_infer_code("invalid_pattern")
1188        .with_span_label(&span, "cannot pattern match on functions")
1189        .with_help("Functions cannot be compared for equality")
1190}
1191
1192pub fn cannot_match_on_unknown(span: Span) -> LisetteDiagnostic {
1193    LisetteDiagnostic::error("Cannot match on Unknown")
1194        .with_infer_code("cannot_match_on_unknown")
1195        .with_span_label(&span, "is type `Unknown`")
1196        .with_help("Use `assert_type` to narrow this value into a concrete type before matching. Example: `let value = assert_type<MyType>(x)?`")
1197}
1198
1199pub fn cannot_match_on_unconstrained_type(span: Span) -> LisetteDiagnostic {
1200    LisetteDiagnostic::error("Uninferred type")
1201        .with_infer_code("cannot_match_on_unconstrained_type")
1202        .with_span_label(&span, "type cannot be inferred at this point")
1203        .with_help("Add a type annotation on the value before matching on it")
1204}
1205
1206pub fn duplicate_binding_in_pattern(
1207    name: &str,
1208    first_span: Span,
1209    second_span: Span,
1210) -> LisetteDiagnostic {
1211    LisetteDiagnostic::error("Duplicate binding")
1212        .with_infer_code("duplicate_binding_in_pattern")
1213        .with_span_label(&first_span, format!("first use of `{}`", name))
1214        .with_span_label(&second_span, "used again")
1215        .with_help("Remove the duplicate binding")
1216}
1217
1218pub fn literal_pattern_in_binding(span: Span) -> LisetteDiagnostic {
1219    LisetteDiagnostic::error("Pattern might not match")
1220        .with_infer_code("literal_in_binding")
1221        .with_span_label(&span, "value might not equal this literal")
1222        .with_help("Use `match` or `if` to compare values")
1223}
1224
1225pub fn as_binding_in_irrefutable_context(span: Span) -> LisetteDiagnostic {
1226    LisetteDiagnostic::error("Invalid `as` binding")
1227        .with_infer_code("as_binding_in_irrefutable_context")
1228        .with_span_label(&span, "`as` is disallowed here")
1229        .with_help("Use `as` only in `match`, `if let`, and `while let`")
1230}
1231
1232pub fn select_some_as_binding_not_supported(span: Span) -> LisetteDiagnostic {
1233    LisetteDiagnostic::error("Cannot alias `Some(...)` in select")
1234        .with_infer_code("select_some_as_not_supported")
1235        .with_span_label(&span, "`as` cannot be placed around `Some(...)`")
1236        .with_help(
1237            "Place `as` inside `Some(...)` to bind the received value: `Some(value as alias)`",
1238        )
1239}
1240
1241pub fn redundant_as_identifier(inner: &str, alias: &str, span: Span) -> LisetteDiagnostic {
1242    LisetteDiagnostic::error("Redundant `as` binding")
1243        .with_infer_code("redundant_as_binding")
1244        .with_span_label(&span, format!("`{}` already binds this value", inner))
1245        .with_help(format!(
1246            "Use `{}` directly, or rename `{}` to `{}`",
1247            alias, inner, alias
1248        ))
1249}
1250
1251pub fn redundant_as_wildcard(alias: &str, span: Span) -> LisetteDiagnostic {
1252    LisetteDiagnostic::error("Redundant `as` binding")
1253        .with_infer_code("redundant_as_binding")
1254        .with_span_label(&span, "`_` binds nothing")
1255        .with_help(format!("Replace `_ as {}` with just `{}`", alias, alias))
1256}
1257
1258pub fn redundant_as_literal(literal: &str, alias: &str, span: Span) -> LisetteDiagnostic {
1259    LisetteDiagnostic::error("Redundant `as` binding")
1260        .with_infer_code("redundant_as_binding")
1261        .with_span_label(&span, format!("`{}` is always `{}`", alias, literal))
1262        .with_help(format!(
1263            "Replace `{} as {}` with just `{}`",
1264            literal, alias, literal
1265        ))
1266}
1267
1268pub fn or_pattern_in_irrefutable_context(span: Span) -> LisetteDiagnostic {
1269    LisetteDiagnostic::error("Invalid or-pattern")
1270        .with_infer_code("or_pattern_in_irrefutable")
1271        .with_span_label(&span, "or-patterns are not allowed here")
1272        .with_help("Use a `match` expression instead.")
1273        .with_note("Or-patterns can only be used in `match`, `if let`, and `while let`.")
1274}
1275
1276pub fn or_pattern_binding_mismatch(
1277    span: Span,
1278    missing_in_later: &[&str],
1279    missing_in_first: &[&str],
1280) -> LisetteDiagnostic {
1281    let missing = if !missing_in_later.is_empty() {
1282        missing_in_later.join(", ")
1283    } else {
1284        missing_in_first.join(", ")
1285    };
1286
1287    LisetteDiagnostic::error("Invalid or-pattern")
1288        .with_infer_code("or_pattern_binding_mismatch")
1289        .with_span_label(&span, "only bound here")
1290        .with_help(format!(
1291            "Variable {} is not bound in all alternatives. Use a wildcard `_` instead of a binding, or ensure all alternatives bind the same variable",
1292            missing
1293        ))
1294}
1295
1296pub fn or_pattern_type_mismatch(span: Span, first_ty: &str, alt_ty: &str) -> LisetteDiagnostic {
1297    LisetteDiagnostic::error("Invalid or-pattern")
1298        .with_infer_code("or_pattern_type_mismatch")
1299        .with_span_label(
1300            &span,
1301            format!("expected `{}`, found `{}`", first_ty, alt_ty),
1302        )
1303        .with_help(
1304            "Use a wildcard `_` instead of a binding, or use separate match arms for each variant",
1305        )
1306}
1307
1308pub fn unknown_iterable_type(span: Span) -> LisetteDiagnostic {
1309    LisetteDiagnostic::error("Uninferrable type")
1310        .with_infer_code("type_not_inferred")
1311        .with_span_label(&span, "cannot be inferred")
1312        .with_help("Add a type annotation to the iterable expression")
1313}
1314
1315pub fn not_iterable(ty: &Type, span: Span) -> LisetteDiagnostic {
1316    LisetteDiagnostic::error("Not iterable")
1317        .with_infer_code("not_iterable")
1318        .with_span_label(&span, format!("`{}` is not iterable", ty))
1319        .with_help("Use `Slice`, `Map`, `Range`, `Channel`, or `string`")
1320}
1321
1322pub fn tuple_literal_required_in_loop(span: Span) -> LisetteDiagnostic {
1323    LisetteDiagnostic::error("Invalid loop pattern")
1324        .with_infer_code("invalid_pattern")
1325        .with_span_label(&span, "tuple literal required here")
1326        .with_help("Use `(key, value)` destructuring pattern for map or enumerated iteration")
1327}
1328
1329pub fn propagate_on_partial(span: Span) -> LisetteDiagnostic {
1330    LisetteDiagnostic::error("Cannot use `?` on `Partial`")
1331        .with_infer_code("propagate_on_partial")
1332        .with_span_label(&span, "`Partial` requires explicit `match`")
1333        .with_help(
1334            "The `?` operator is incompatible with `Partial` because it has \
1335             three variants. Use `match` to handle `Ok`, `Err`, and `Both` \
1336             explicitly.",
1337        )
1338}
1339
1340pub fn try_requires_result_or_option(span: Span) -> LisetteDiagnostic {
1341    LisetteDiagnostic::error("Type mismatch")
1342        .with_infer_code("try_requires_result_or_option")
1343        .with_span_label(&span, "expects `Result` or `Option`")
1344        .with_help("Use the `?` operator only on `Result` or `Option`")
1345}
1346
1347pub fn try_outside_function(span: Span) -> LisetteDiagnostic {
1348    LisetteDiagnostic::error("`?` outside function")
1349        .with_infer_code("try_outside_function")
1350        .with_span_label(&span, "`?` outside function")
1351        .with_help("Use `?` only inside a function that returns `Result` or `Option`")
1352}
1353
1354pub fn try_return_type_mismatch(expected: &str, actual_ty: &Type, span: Span) -> LisetteDiagnostic {
1355    LisetteDiagnostic::error("Type mismatch")
1356        .with_infer_code("try_return_type_mismatch")
1357        .with_span_label(
1358            &span,
1359            format!(
1360                "expects `{}`, but function returns `{}`",
1361                expected, actual_ty
1362            ),
1363        )
1364        .with_help(format!(
1365            "Change the function return type to `{}` or remove the `?` operator",
1366            expected
1367        ))
1368}
1369
1370pub fn try_block_empty(span: Span) -> LisetteDiagnostic {
1371    LisetteDiagnostic::error("Empty `try` block")
1372        .with_infer_code("try_block_empty")
1373        .with_span_label(&span, "empty")
1374        .with_help("Ensure the `try` block contains at least one expression")
1375}
1376
1377pub fn try_block_no_question_mark(try_keyword_span: Span) -> LisetteDiagnostic {
1378    LisetteDiagnostic::error("Useless `try` block")
1379        .with_infer_code("try_block_no_question_mark")
1380        .with_span_label(&try_keyword_span, "no `?` operator found")
1381        .with_help("A `try` block must contain at least one `?` for propagation")
1382}
1383
1384pub fn mixed_carriers_in_try_block(span: Span) -> LisetteDiagnostic {
1385    LisetteDiagnostic::error("Mixed `try` block")
1386        .with_infer_code("try_block_mixed_carriers")
1387        .with_span_label(&span, "mixing `Option` and `Result`")
1388        .with_help(
1389            "A `try` block must use either all `Option` operations or all `Result` operations",
1390        )
1391}
1392
1393pub fn break_outside_loop(span: Span) -> LisetteDiagnostic {
1394    LisetteDiagnostic::error("`break` outside loop")
1395        .with_infer_code("break_outside_loop")
1396        .with_span_label(&span, "not inside a loop")
1397        .with_help("`break` can only be used inside `loop`, `for`, or `while`")
1398}
1399
1400pub fn continue_outside_loop(span: Span) -> LisetteDiagnostic {
1401    LisetteDiagnostic::error("`continue` outside loop")
1402        .with_infer_code("continue_outside_loop")
1403        .with_span_label(&span, "not inside a loop")
1404        .with_help("`continue` can only be used inside `loop`, `for`, or `while`")
1405}
1406
1407pub fn nested_function(span: Span) -> LisetteDiagnostic {
1408    LisetteDiagnostic::error("Nested function declaration")
1409        .with_infer_code("nested_function")
1410        .with_span_label(&span, "functions can only be declared at top level")
1411        .with_help("Use a lambda instead: `|x| x + 1` or `|x| { ... }`")
1412}
1413
1414pub fn return_in_try_block(span: Span) -> LisetteDiagnostic {
1415    LisetteDiagnostic::error("`return` in `try` block")
1416        .with_infer_code("try_block_return")
1417        .with_span_label(&span, "not inside a function")
1418        .with_help(
1419            "Use `return` inside a function, or use `Err(...)?` to exit the `try` block early",
1420        )
1421}
1422
1423pub fn break_in_try_block(span: Span) -> LisetteDiagnostic {
1424    LisetteDiagnostic::error("`break` in `try` block")
1425        .with_infer_code("try_block_break")
1426        .with_span_label(&span, "not inside a loop")
1427        .with_help("Use `break` inside a loop, or use `Err(...)?` to exit the `try` block early")
1428}
1429
1430pub fn continue_in_try_block(span: Span) -> LisetteDiagnostic {
1431    LisetteDiagnostic::error("`continue` in `try` block")
1432        .with_infer_code("try_block_continue")
1433        .with_span_label(&span, "not inside a loop")
1434        .with_help("Use `continue` inside a loop, or use `Err(...)?` to exit the `try` block early")
1435}
1436
1437pub fn recover_block_empty(span: Span) -> LisetteDiagnostic {
1438    LisetteDiagnostic::warn("Empty `recover` block")
1439        .with_infer_code("recover_block_empty")
1440        .with_span_label(&span, "empty")
1441        .with_help("Ensure the `recover` block contains at least one expression that may panic")
1442}
1443
1444pub fn recover_cannot_use_question_mark(span: Span) -> LisetteDiagnostic {
1445    LisetteDiagnostic::error("`?` in `recover` block")
1446        .with_infer_code("recover_cannot_use_question_mark")
1447        .with_span_label(&span, "cannot propagate to `recover` block")
1448        .with_help(
1449            "Use a `try` block inside the `recover` block, or handle the `Result` explicitly",
1450        )
1451}
1452
1453pub fn return_in_recover_block(span: Span) -> LisetteDiagnostic {
1454    LisetteDiagnostic::error("`return` in `recover` block")
1455        .with_infer_code("recover_block_return")
1456        .with_span_label(&span, "not allowed inside `recover` block")
1457        .with_help("Remove the `return`, or move it inside a nested function")
1458}
1459
1460pub fn break_in_recover_block(span: Span) -> LisetteDiagnostic {
1461    LisetteDiagnostic::error("`break` in `recover` block")
1462        .with_infer_code("recover_block_break")
1463        .with_span_label(&span, "not allowed inside `recover` block")
1464        .with_help("Remove the `break`, or move it inside a loop within the `recover` block")
1465}
1466
1467pub fn continue_in_recover_block(span: Span) -> LisetteDiagnostic {
1468    LisetteDiagnostic::error("`continue` in `recover` block")
1469        .with_infer_code("recover_block_continue")
1470        .with_span_label(&span, "not allowed inside `recover` block")
1471        .with_help("Remove the `continue`, or move it inside a loop within the `recover` block")
1472}
1473
1474pub fn expected_channel_receive(ty: &Type, span: Span) -> LisetteDiagnostic {
1475    LisetteDiagnostic::error("Expected channel receive")
1476        .with_infer_code("expected_channel_receive")
1477        .with_span_label(&span, format!("`{}` is not a channel receive", ty))
1478        .with_help("Use `ch.receive()` to receive from a channel in select")
1479}
1480
1481pub fn empty_select(span: Span) -> LisetteDiagnostic {
1482    LisetteDiagnostic::error("Empty select")
1483        .with_infer_code("empty_select")
1484        .with_span_label(&span, "select has no arms")
1485        .with_help(
1486            "Add at least one channel operation arm, e.g. `select { ch.receive() => v { ... } }`",
1487        )
1488}
1489
1490pub fn expected_channel_send(span: Span) -> LisetteDiagnostic {
1491    LisetteDiagnostic::error("Expected channel operation")
1492        .with_infer_code("expected_channel_send")
1493        .with_span_label(&span, "not a channel operation")
1494        .with_help("Use `ch.send(value)` or `ch.receive()` in select arms")
1495}
1496
1497pub fn bare_identifier_in_select_receive(span: Span) -> LisetteDiagnostic {
1498    LisetteDiagnostic::error("Invalid select case")
1499        .with_infer_code("bare_identifier_in_select_receive")
1500        .with_span_label(&span, "expected destructuring")
1501        .with_help("`ch.receive()` returns an `Option`, so use `let Some(v) = ch.receive()` to bind the value")
1502}
1503
1504pub fn none_pattern_in_select_receive(span: Span) -> LisetteDiagnostic {
1505    LisetteDiagnostic::error("Invalid select case")
1506        .with_infer_code("none_pattern_in_select_receive")
1507        .with_span_label(&span, "expected match")
1508        .with_help(
1509            "To detect channel close, use `match ch.receive() { Some(v) => ..., None => ... }`",
1510        )
1511}
1512
1513pub fn select_match_missing_some_arm(span: Span) -> LisetteDiagnostic {
1514    LisetteDiagnostic::error("Invalid select match")
1515        .with_infer_code("select_match_missing_some_arm")
1516        .with_span_label(&span, "missing `Some` arm")
1517        .with_help("`None` only handles channel close. Add a `Some(v) => ...` arm to handle received values")
1518}
1519
1520pub fn select_match_missing_none_arm(span: Span) -> LisetteDiagnostic {
1521    LisetteDiagnostic::error("Invalid select match")
1522        .with_infer_code("select_match_missing_none_arm")
1523        .with_span_label(&span, "missing `None` arm")
1524        .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() => ...`")
1525}
1526
1527pub fn select_match_duplicate_some_arm(span: Span) -> LisetteDiagnostic {
1528    LisetteDiagnostic::error("Invalid select match")
1529        .with_infer_code("select_match_duplicate_some_arm")
1530        .with_span_label(&span, "duplicate")
1531        .with_help(
1532            "Remove the duplicate `Some` arm. If you need to, use a `match` inside the arm body",
1533        )
1534}
1535
1536pub fn select_match_duplicate_none_arm(span: Span) -> LisetteDiagnostic {
1537    LisetteDiagnostic::error("Invalid select match")
1538        .with_infer_code("select_match_duplicate_none_arm")
1539        .with_span_label(&span, "duplicate")
1540        .with_help(
1541            "Remove the duplicate `None` arm. If you need to, use a `match` inside the arm body",
1542        )
1543}
1544
1545pub fn select_match_guard_not_allowed(span: Span) -> LisetteDiagnostic {
1546    LisetteDiagnostic::error("Invalid select match")
1547        .with_infer_code("select_match_guard_not_allowed")
1548        .with_span_label(&span, "not supported")
1549        .with_help("Match arms inside `select` do not support guards. Move the condition inside the arm body: `Some(v) => { if condition { ... } }`")
1550}
1551
1552pub fn select_match_invalid_pattern(span: Span) -> LisetteDiagnostic {
1553    LisetteDiagnostic::error("Invalid select match")
1554        .with_infer_code("select_match_invalid_pattern")
1555        .with_span_label(&span, "unsupported pattern")
1556        .with_help("Select match arms support only `Some(...)` and `None` patterns")
1557}
1558
1559pub fn select_receive_refutable_pattern(span: Span) -> LisetteDiagnostic {
1560    LisetteDiagnostic::error("Refutable pattern in select receive")
1561        .with_infer_code("select_receive_refutable_pattern")
1562        .with_span_label(&span, "may not match all received values")
1563        .with_help(
1564            "Select receive requires an irrefutable binding like `Some(v)` or `Some(_)`. \
1565             Use a regular `match` inside the arm body to filter values",
1566        )
1567}
1568
1569pub fn multiple_select_receives(first_span: Span, second_span: Span) -> LisetteDiagnostic {
1570    LisetteDiagnostic::error("Invalid select")
1571        .with_infer_code("multiple_select_receives")
1572        .with_span_label(&first_span, "first receive arm")
1573        .with_span_label(&second_span, "second receive arm")
1574        .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")
1575}
1576
1577pub fn duplicate_select_default(first_span: Span, second_span: Span) -> LisetteDiagnostic {
1578    LisetteDiagnostic::error("Invalid select")
1579        .with_infer_code("duplicate_select_default")
1580        .with_span_label(&first_span, "first default arm")
1581        .with_span_label(&second_span, "duplicate default arm")
1582        .with_help(
1583            "A select block can have at most one default arm (`_ => ...`). Remove the duplicate.",
1584        )
1585}
1586
1587pub fn non_exhaustive_select_expression(span: Span) -> LisetteDiagnostic {
1588    LisetteDiagnostic::error("Non-exhaustive select expression")
1589        .with_infer_code("non_exhaustive_select_expression")
1590        .with_span_label(&span, "may not produce a value")
1591        .with_help("Add a default arm `_ => ...` to handle closed channels")
1592}
1593
1594pub fn type_must_be_known(span: Span) -> LisetteDiagnostic {
1595    LisetteDiagnostic::error("Uninferrable type")
1596        .with_infer_code("type_not_inferred")
1597        .with_span_label(&span, "cannot be inferred")
1598        .with_help("Add a type annotation to help the compiler infer the type")
1599}
1600
1601pub fn uninferred_binding(name: &str, span: Span) -> LisetteDiagnostic {
1602    LisetteDiagnostic::error("Uninferrable type")
1603        .with_infer_code("type_not_inferred")
1604        .with_span_label(&span, "cannot be inferred")
1605        .with_help(format!(
1606            "Add a type annotation. For example: `let {}: Slice<int> = ...`",
1607            name
1608        ))
1609}
1610
1611pub fn unconstrained_type_param(param_name: &str, span: Span) -> LisetteDiagnostic {
1612    LisetteDiagnostic::error("Unconstrained type parameter")
1613        .with_infer_code("unconstrained_type_param")
1614        .with_span_label(
1615            &span,
1616            format!(
1617                "`{}` is not constrained by parameters or return type",
1618                param_name
1619            ),
1620        )
1621        .with_help(format!(
1622            "Use `{}` in a parameter or return type, or provide an explicit type argument: `func<SomeType>(...)`",
1623            param_name
1624        ))
1625}
1626
1627pub fn slice_index_type_mismatch(index_ty: &Type, span: Span) -> LisetteDiagnostic {
1628    LisetteDiagnostic::error("Type mismatch")
1629        .with_infer_code("slice_index_type_mismatch")
1630        .with_span_label(&span, format!("expected `int`, found `{}`", index_ty))
1631        .with_help(
1632            "Use an integer to index into a `Slice`. For key-value lookup, use a `Map<K, V>`",
1633        )
1634}
1635
1636pub fn only_slices_and_maps_indexable(ty: &Type, span: Span) -> LisetteDiagnostic {
1637    LisetteDiagnostic::error("Not indexable")
1638        .with_infer_code("not_indexable")
1639        .with_span_label(&span, format!("expected `Slice` or `Map`, found `{}`", ty))
1640        .with_help("Only `Slice` and `Map` can be indexed into")
1641}
1642
1643pub fn string_not_indexable(span: Span, receiver: &str) -> LisetteDiagnostic {
1644    LisetteDiagnostic::error("Cannot index into `string`")
1645        .with_infer_code("string_not_indexable")
1646        .with_span_label(&span, "not indexable")
1647        .with_help(format!(
1648            "Use `{receiver}.rune_at(i)` to get a `rune`, or `{receiver}.byte_at(i)` to get a `byte`"
1649        ))
1650}
1651
1652pub fn string_not_sliceable(span: Span, receiver: &str) -> LisetteDiagnostic {
1653    LisetteDiagnostic::error("Cannot slice into `string`")
1654        .with_infer_code("string_not_sliceable")
1655        .with_span_label(&span, "not sliceable")
1656        .with_help(format!(
1657            "Use `{receiver}.substring(a..b)` for a rune-indexed substring, or `{receiver}.bytes()[a..b]` for a range of bytes"
1658        ))
1659}
1660
1661pub fn string_not_iterable(span: Span, receiver: &str) -> LisetteDiagnostic {
1662    LisetteDiagnostic::error("Cannot iterate over `string`")
1663        .with_infer_code("string_not_iterable")
1664        .with_span_label(&span, "not iterable")
1665        .with_help(format!(
1666            "Use `for r in {receiver}.runes()` for code points, or `for b in {receiver}.bytes()` for bytes"
1667        ))
1668}
1669
1670pub fn colon_in_subscript(
1671    span: Span,
1672    receiver: &str,
1673    type_name: Option<&str>,
1674) -> LisetteDiagnostic {
1675    let (message, label, help) = match type_name {
1676        Some("string") => (
1677            "Invalid syntax for string slicing",
1678            "expected a method call",
1679            format!(
1680                "Use `{receiver}.substring(a..b)` for a string, or `{receiver}.bytes()[a..b]` for a range of bytes"
1681            ),
1682        ),
1683        _ => (
1684            "Invalid syntax for subslicing",
1685            "expected `..`",
1686            format!(
1687                "Use `{receiver}[a..b]` or `{receiver}[a..=b]` for an exclusive or inclusive slice, respectively"
1688            ),
1689        ),
1690    };
1691    LisetteDiagnostic::error(message)
1692        .with_parse_code("colon_in_subscript")
1693        .with_span_label(&span, label)
1694        .with_help(help)
1695}
1696
1697pub fn not_callable(
1698    ty: &Type,
1699    callee_name: Option<&str>,
1700    arg_name: Option<&str>,
1701    span: Span,
1702) -> LisetteDiagnostic {
1703    let type_name = ty.get_name();
1704    let is_type_call = matches!((callee_name, type_name), (Some(c), Some(t)) if c == t);
1705    let is_cast_target = ty.get_underlying().is_some()
1706        || type_name.is_some_and(|n| SimpleKind::from_name(n).is_some());
1707
1708    let help = if is_type_call && is_cast_target {
1709        let subject = arg_name.unwrap_or("value");
1710        format!(
1711            "Use `{} as {}` to cast between types",
1712            subject,
1713            type_name.unwrap()
1714        )
1715    } else {
1716        "Only functions can be called with `()`".to_string()
1717    };
1718
1719    LisetteDiagnostic::error("Not callable")
1720        .with_infer_code("not_callable")
1721        .with_span_label(&span, format!("expected function, found `{}`", ty))
1722        .with_help(help)
1723}
1724
1725pub fn type_conversion_arity(
1726    type_name: &str,
1727    actual_count: usize,
1728    span: Span,
1729) -> LisetteDiagnostic {
1730    LisetteDiagnostic::error("Wrong argument count")
1731        .with_infer_code("type_conversion_arity")
1732        .with_span_label(
1733            &span,
1734            format!("expected 1 argument, found {}", actual_count),
1735        )
1736        .with_help(format!(
1737            "Type conversion `{}(value)` takes exactly one argument — the value to convert",
1738            type_name
1739        ))
1740}
1741
1742#[derive(Debug, Clone)]
1743pub struct InterfaceViolation {
1744    pub interface_name: String,
1745    pub parent_of: Option<String>,
1746    pub missing: Vec<(String, Type)>,
1747    pub incompatible: Vec<(String, Type, Type)>,
1748}
1749
1750pub fn interface_not_implemented(
1751    interface_name: &str,
1752    type_name: &str,
1753    violations: &[InterfaceViolation],
1754    span: Span,
1755) -> LisetteDiagnostic {
1756    let mut help_lines = Vec::new();
1757
1758    let mut missing_sections: Vec<(String, Vec<String>)> = Vec::new();
1759    let mut incompatible_sections: Vec<(String, Vec<String>)> = Vec::new();
1760
1761    for violation in violations {
1762        let header = if let Some(ref parent) = violation.parent_of {
1763            format!(
1764                "From `{}` (required by `{}`)",
1765                violation.interface_name, parent
1766            )
1767        } else {
1768            format!("From `{}`", violation.interface_name)
1769        };
1770
1771        if !violation.missing.is_empty() {
1772            let methods: Vec<String> = violation
1773                .missing
1774                .iter()
1775                .map(|(name, sig)| format!("  - {}: {}", name, sig))
1776                .collect();
1777            missing_sections.push((header.clone(), methods));
1778        }
1779
1780        if !violation.incompatible.is_empty() {
1781            let methods: Vec<String> = violation
1782                .incompatible
1783                .iter()
1784                .map(|(name, expected, actual)| {
1785                    format!("  - {}: expected `{}`, found `{}`", name, expected, actual)
1786                })
1787                .collect();
1788            incompatible_sections.push((header, methods));
1789        }
1790    }
1791
1792    if !missing_sections.is_empty() {
1793        help_lines.push("Missing methods:".to_string());
1794        for (header, methods) in &missing_sections {
1795            help_lines.push(format!("  {}", header));
1796            for method in methods {
1797                help_lines.push(format!("  {}", method));
1798            }
1799        }
1800    }
1801
1802    if !incompatible_sections.is_empty() {
1803        help_lines.push("Incompatible methods:".to_string());
1804        for (header, methods) in &incompatible_sections {
1805            help_lines.push(format!("  {}", header));
1806            for method in methods {
1807                help_lines.push(format!("  {}", method));
1808            }
1809        }
1810    }
1811
1812    LisetteDiagnostic::error("Interface not implemented")
1813        .with_infer_code("interface_not_implemented")
1814        .with_span_label(
1815            &span,
1816            format!("`{}` does not implement `{}`", type_name, interface_name),
1817        )
1818        .with_help(help_lines.join("\n"))
1819}
1820
1821#[derive(Debug, Clone, Copy)]
1822pub enum WrapperKind {
1823    Result,
1824    Option,
1825    Partial,
1826}
1827
1828pub fn wrapper_does_not_implement_interface(
1829    interface_name: &str,
1830    wrapper: WrapperKind,
1831    wrapper_ty: &Type,
1832    span: Span,
1833) -> LisetteDiagnostic {
1834    let help = match wrapper {
1835        WrapperKind::Result => "Unwrap the `Result` with `match` or `if let` first.",
1836        WrapperKind::Option => "Unwrap the `Option` with `match` or `if let` first.",
1837        WrapperKind::Partial => "Unwrap the `Partial` with `match` first.",
1838    };
1839    LisetteDiagnostic::error("Interface not implemented")
1840        .with_infer_code("interface_not_implemented")
1841        .with_span_label(
1842            &span,
1843            format!("`{}` does not implement `{}`", wrapper_ty, interface_name),
1844        )
1845        .with_help(help)
1846}
1847
1848pub fn pointer_receiver_interface_mismatch(
1849    interface_name: &str,
1850    type_name: &str,
1851    methods: &[String],
1852    span: Span,
1853) -> LisetteDiagnostic {
1854    let methods_str = methods
1855        .iter()
1856        .map(|m| format!("`{}.{}`", type_name, m))
1857        .collect::<Vec<_>>()
1858        .join(", ");
1859    let takes = if methods.len() == 1 {
1860        format!("{} takes `self: Ref<{}>`", methods_str, type_name)
1861    } else {
1862        format!("{} take `self: Ref<{}>`", methods_str, type_name)
1863    };
1864    LisetteDiagnostic::error("Interface not implemented")
1865        .with_infer_code("interface_not_implemented")
1866        .with_span_label(
1867            &span,
1868            format!("`{}` does not implement `{}`", type_name, interface_name),
1869        )
1870        .with_help(format!("{}, so pass a `Ref<{}>`.", takes, type_name))
1871}
1872
1873pub fn unknown_in_bound_position(span: Span) -> LisetteDiagnostic {
1874    LisetteDiagnostic::error("Invalid `Unknown` bound")
1875        .with_infer_code("unknown_in_bound_position")
1876        .with_span_label(&span, "invalid bound")
1877        .with_help("`Unknown` cannot constrain a generic")
1878}
1879
1880pub fn unknown_in_const_annotation(span: Span) -> LisetteDiagnostic {
1881    LisetteDiagnostic::error("Invalid `Unknown` in `const` annotation")
1882        .with_infer_code("unknown_in_const_annotation")
1883        .with_span_label(&span, "invalid annotation")
1884        .with_help("`Unknown` cannot be used to annotate a constant")
1885}
1886
1887pub fn unknown_as_map_key(span: Span) -> LisetteDiagnostic {
1888    LisetteDiagnostic::error("`Unknown` cannot be used as a map key")
1889        .with_infer_code("unknown_as_map_key")
1890        .with_span_label(&span, "key resolves to `any`")
1891        .with_help("Use a concrete comparable key type.")
1892        .with_note("Go's `map[any]V` admits non-comparable runtime values that panic on insertion.")
1893}
1894
1895pub fn opaque_type_outside_typedef(span: Span) -> LisetteDiagnostic {
1896    LisetteDiagnostic::error("Undefined type")
1897        .with_infer_code("undefined_type_outside_typedef")
1898        .with_span_label(&span, "needs a definition")
1899        .with_help("Use `type Point = ...` to define the type.")
1900        .with_note("Opaque declarations are only allowed in `.d.lis` files.")
1901}
1902
1903pub fn bodyless_function_outside_typedef(span: Span) -> LisetteDiagnostic {
1904    LisetteDiagnostic::error("Missing function body")
1905        .with_infer_code("bodyless_function_outside_typedef")
1906        .with_span_label(&span, "needs a body")
1907        .with_help("Add a body: `fn greet() { ... }`.")
1908        .with_note("Bodyless declarations are only allowed in `.d.lis` files.")
1909}
1910
1911pub fn valueless_const_outside_typedef(span: Span) -> LisetteDiagnostic {
1912    LisetteDiagnostic::error("Missing const value")
1913        .with_infer_code("valueless_const_outside_typedef")
1914        .with_span_label(&span, "needs a value")
1915        .with_help("Ensure the constant has a value: `const MAX_SIZE: int = 100`.")
1916        .with_note("Valueless const declarations are only allowed in `.d.lis` files.")
1917}
1918
1919pub fn valueless_const_missing_annotation(span: Span) -> LisetteDiagnostic {
1920    LisetteDiagnostic::error("Missing const annotation")
1921        .with_infer_code("valueless_const_missing_annotation")
1922        .with_span_label(&span, "needs a type annotation")
1923        .with_help("Valueless const declarations require a type annotation: `const MAX_SIZE: int`")
1924}
1925
1926pub fn variable_declaration_outside_typedef(span: Span) -> LisetteDiagnostic {
1927    LisetteDiagnostic::error("Invalid variable declaration")
1928        .with_infer_code("variable_declaration_outside_typedef")
1929        .with_span_label(&span, "`var` is not allowed here")
1930        .with_help("Use `let` to declare a variable: `let x: int = 0`.")
1931        .with_note("`var` declarations are only allowed in `.d.lis` files.")
1932}
1933
1934pub fn range_full_not_valid_expression(span: Span) -> LisetteDiagnostic {
1935    LisetteDiagnostic::error("Invalid expression")
1936        .with_infer_code("range_full_not_expression")
1937        .with_span_label(&span, "`..` can only be used in slice indexing")
1938        .with_help("Use `arr[..]` to get a full slice, or provide bounds like `0..10`")
1939}
1940
1941pub fn range_not_iterable(range_type: &str, span: Span) -> LisetteDiagnostic {
1942    LisetteDiagnostic::error("Not iterable")
1943        .with_infer_code("range_not_iterable")
1944        .with_span_label(&span, format!("`{}` has no start bound", range_type))
1945        .with_help("Use a range with a start bound, e.g. `0..10` instead of `..10`")
1946}
1947
1948pub fn taking_value_of_ufcs_method(span: Span) -> LisetteDiagnostic {
1949    LisetteDiagnostic::error("Invalid method value")
1950        .with_infer_code("taking_value_of_ufcs_method")
1951        .with_span_label(&span, "taking value not allowed")
1952        .with_help(
1953            "This method cannot be taken as a value. Call the method directly: `obj.method(...)`",
1954        )
1955}
1956
1957pub fn duplicate_definition(kind: &str, name: &str, span: Span) -> LisetteDiagnostic {
1958    LisetteDiagnostic::error(format!("Duplicate {}", kind))
1959        .with_infer_code("duplicate_definition")
1960        .with_span_label(&span, "already defined")
1961        .with_help(format!(
1962            "`{}` is already defined in this module. Rename or remove this definition.",
1963            name
1964        ))
1965}
1966
1967pub fn duplicate_impl_item(item_name: &str, type_name: &str, span: Span) -> LisetteDiagnostic {
1968    LisetteDiagnostic::error("Duplicate name in impl")
1969        .with_infer_code("duplicate_impl_item")
1970        .with_span_label(&span, "method name already taken")
1971        .with_help(format!(
1972            "Method `{}` is already defined for type `{}`. Rename one of the methods.",
1973            item_name, type_name
1974        ))
1975}
1976
1977pub fn duplicate_method_across_specialized_impls(
1978    method_name: &str,
1979    type_name: &str,
1980    generics: &[String],
1981    span: Span,
1982) -> LisetteDiagnostic {
1983    let params = generics.join(", ");
1984    LisetteDiagnostic::error("Duplicate method across specialized `impl` blocks")
1985        .with_infer_code("duplicate_method_across_specialized_impls")
1986        .with_span_label(&span, "already defined in another specialization")
1987        .with_help(format!(
1988            "Specialized `impl` blocks for `{type_name}` share a method namespace. \
1989             Use different method names, or move `{method_name}` to a generic `impl<{params}> {type_name}<{params}> {{}}` block."
1990        ))
1991}
1992
1993pub fn method_shadows_field(type_name: &str, field_name: &str, span: Span) -> LisetteDiagnostic {
1994    LisetteDiagnostic::error("Method shadows struct field")
1995        .with_infer_code("method_shadows_field")
1996        .with_span_label(&span, "same as field")
1997        .with_help(format!(
1998            "`{}` has a field `{}` and a method `{}`. Rename either the field or the method",
1999            type_name, field_name, field_name
2000        ))
2001}
2002
2003pub fn non_int_range_not_iterable(element_ty: &Type, span: Span) -> LisetteDiagnostic {
2004    LisetteDiagnostic::error("Not iterable")
2005        .with_infer_code("non_int_range_not_iterable")
2006        .with_span_label(
2007            &span,
2008            format!("cannot iterate over `Range<{}>`", element_ty),
2009        )
2010        .with_help("Range iteration requires integer bounds")
2011}
2012
2013pub fn only_slices_indexable_by_range(ty: &Type, span: Span) -> LisetteDiagnostic {
2014    LisetteDiagnostic::error("Type mismatch")
2015        .with_infer_code("range_index_not_slice")
2016        .with_span_label(
2017            &span,
2018            format!("expected `Slice` or `string`, found `{}`", ty),
2019        )
2020        .with_help("Range indexing only works on `Slice` and `string`")
2021}
2022
2023pub fn empty_body_return_mismatch(expected_ty: &Type, span: Span) -> LisetteDiagnostic {
2024    LisetteDiagnostic::error("Type mismatch")
2025        .with_infer_code("type_mismatch")
2026        .with_span_label(
2027            &span,
2028            format!("promises `{}`, but returns `()`", expected_ty),
2029        )
2030        .with_help("Return a value or change the return type annotation to `()`.")
2031        .with_note("An empty function body implicitly returns `()`.")
2032}
2033
2034fn operator_verb(operator: &BinaryOperator) -> &'static str {
2035    match operator {
2036        BinaryOperator::Addition => "add",
2037        BinaryOperator::Subtraction => "subtract",
2038        BinaryOperator::Multiplication => "multiply",
2039        BinaryOperator::Division => "divide",
2040        BinaryOperator::Remainder => "get remainder of",
2041        BinaryOperator::BitwiseAnd
2042        | BinaryOperator::BitwiseOr
2043        | BinaryOperator::BitwiseXor
2044        | BinaryOperator::BitwiseAndNot => "apply bitwise operator to",
2045        BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => "shift",
2046        BinaryOperator::Equal | BinaryOperator::NotEqual => "compare",
2047        BinaryOperator::LessThan
2048        | BinaryOperator::LessThanOrEqual
2049        | BinaryOperator::GreaterThan
2050        | BinaryOperator::GreaterThanOrEqual => "compare",
2051        BinaryOperator::And | BinaryOperator::Or => "apply logical operator to",
2052        BinaryOperator::Pipeline => "pipe",
2053    }
2054}
2055
2056fn operator_help(op: &BinaryOperator) -> &'static str {
2057    match op {
2058        BinaryOperator::Addition => "requires both operands to have the same type",
2059        BinaryOperator::Subtraction
2060        | BinaryOperator::Multiplication
2061        | BinaryOperator::Division
2062        | BinaryOperator::Remainder
2063        | BinaryOperator::BitwiseAnd
2064        | BinaryOperator::BitwiseOr
2065        | BinaryOperator::BitwiseXor
2066        | BinaryOperator::BitwiseAndNot => "requires both operands to have the same integer type",
2067        BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => {
2068            "requires integer operands (the result type comes from the left operand)"
2069        }
2070        BinaryOperator::Equal | BinaryOperator::NotEqual => {
2071            "requires both operands to have the same type"
2072        }
2073        BinaryOperator::LessThan
2074        | BinaryOperator::LessThanOrEqual
2075        | BinaryOperator::GreaterThan
2076        | BinaryOperator::GreaterThanOrEqual => "requires both operands to have the same type",
2077        BinaryOperator::And | BinaryOperator::Or => "requires both operands to be bool",
2078        BinaryOperator::Pipeline => "should have been desugared",
2079    }
2080}
2081
2082pub fn task_in_expression_position(span: Span) -> LisetteDiagnostic {
2083    LisetteDiagnostic::error("Invalid `task`")
2084        .with_infer_code("task_in_expression_position")
2085        .with_span_label(&span, "produces no value")
2086        .with_help("Move `task` to its own statement")
2087}
2088
2089pub fn defer_in_expression_position(span: Span) -> LisetteDiagnostic {
2090    LisetteDiagnostic::error("Invalid `defer`")
2091        .with_infer_code("defer_in_expression_position")
2092        .with_span_label(&span, "produces no value")
2093        .with_help("Move `defer` to its own statement")
2094}
2095
2096pub fn non_addressable_expression(expression_kind: &str, span: Span) -> LisetteDiagnostic {
2097    LisetteDiagnostic::error("Non-addressable expression")
2098        .with_infer_code("non_addressable_expression")
2099        .with_span_label(&span, format!("cannot take address of {}", expression_kind))
2100        .with_help("Assign the value to a variable first, then take its address")
2101}
2102
2103pub fn non_addressable_const(span: Span) -> LisetteDiagnostic {
2104    LisetteDiagnostic::error("Cannot take address of `const`")
2105        .with_infer_code("non_addressable_const")
2106        .with_span_label(&span, "not addressable")
2107        .with_help(
2108            "`const` bindings are not addressable. Copy the value into a local `let` first if you need a reference",
2109        )
2110}
2111
2112pub fn non_addressable_assignment(expression_kind: &str, span: Span) -> LisetteDiagnostic {
2113    LisetteDiagnostic::error("Cannot assign to non-addressable expression")
2114        .with_infer_code("non_addressable_assignment")
2115        .with_span_label(&span, format!("cannot assign to {}", expression_kind))
2116        .with_help("Assign the value to a variable first, then modify it")
2117}
2118
2119pub fn newtype_field_assignment(type_name: &str, span: Span) -> LisetteDiagnostic {
2120    LisetteDiagnostic::error("Cannot assign to newtype field")
2121        .with_infer_code("newtype_field_assignment")
2122        .with_span_label(&span, "newtype fields are read-only")
2123        .with_help(format!(
2124            "Reconstruct the newtype: `variable = {type_name}(new_value)`"
2125        ))
2126}
2127
2128pub fn interpolation_without_stringer(
2129    type_name: &str,
2130    span: Span,
2131    pointer_newtype: bool,
2132) -> LisetteDiagnostic {
2133    let help = if pointer_newtype {
2134        "Interpolate the inner value directly, or change the representation".to_string()
2135    } else {
2136        format!("Mark `{type_name}` with `#[display]`, or interpolate its fields directly.")
2137    };
2138    LisetteDiagnostic::error(format!("`{type_name}` cannot be interpolated"))
2139        .with_infer_code("interpolation_without_stringer")
2140        .with_span_label(&span, "has no display form")
2141        .with_help(help)
2142}
2143
2144pub fn complex_select_expression(span: Span) -> LisetteDiagnostic {
2145    LisetteDiagnostic::error("Complex expression in `select` arm")
2146        .with_infer_code("complex_select_expression")
2147        .with_span_label(&span, "expected simple expression")
2148        .with_help("Hoist to a `let` binding before the `select`")
2149}
2150
2151pub fn ref_slice_append(span: Span) -> LisetteDiagnostic {
2152    LisetteDiagnostic::error("Cannot call append/extend on `Ref<Slice>`")
2153        .with_infer_code("ref_slice_append")
2154        .with_span_label(&span, "dereference the ref first")
2155        .with_help("Use `r.*.append(x)` to deref, then append")
2156}
2157
2158pub fn map_field_chain_assignment(span: Span) -> LisetteDiagnostic {
2159    LisetteDiagnostic::error("Cannot assign to field of map entry")
2160        .with_infer_code("map_field_chain_assignment")
2161        .with_span_label(&span, "assignment not allowed here")
2162        .with_help(
2163            "Extract, modify, and reinsert: `let mut entry = m[key]; entry.field = value; m[key] = entry`",
2164        )
2165}
2166
2167pub fn enum_field_type_conflict(
2168    loc_a: &str,
2169    type_a: &str,
2170    loc_b: &str,
2171    type_b: &str,
2172    span: Span,
2173) -> LisetteDiagnostic {
2174    LisetteDiagnostic::error("Conflicting field types across enum variants")
2175        .with_infer_code("enum_field_type_conflict")
2176        .with_span_label(&span, "field type mismatch")
2177        .with_help(format!(
2178            "`{loc_a}` is `{type_a}` but `{loc_b}` is `{type_b}`. Rename one of the fields or align their types",
2179        ))
2180}
2181
2182pub fn cannot_auto_address_receiver(
2183    receiver_kind: &str,
2184    method_name: &str,
2185    expected_ty: &Type,
2186    actual_ty: &Type,
2187    span: Span,
2188) -> LisetteDiagnostic {
2189    let readable_kind = match receiver_kind {
2190        "map index expression" => "map lookup",
2191        "function call" => "function result",
2192        "literal" => "literal",
2193        "binary expression" => "expression result",
2194        "conditional expression" => "conditional result",
2195        "match expression" => "match result",
2196        "block expression" => "block result",
2197        "lambda" => "lambda",
2198        "tuple" => "tuple",
2199        "range expression" => "range expression",
2200        _ => "expression",
2201    };
2202
2203    LisetteDiagnostic::error("Expression not modifiable")
2204        .with_infer_code("cannot_auto_address_receiver")
2205        .with_span_label(&span, "modifies its receiver")
2206        .with_help(format!(
2207            "Assign the {} to a variable first, then call the method. The receiver of `{}` is `{}`, not `{}`",
2208            readable_kind, method_name, expected_ty, actual_ty
2209        ))
2210}
2211
2212pub fn break_value_in_non_loop(span: Span) -> LisetteDiagnostic {
2213    LisetteDiagnostic::error("`break` with value in non-`loop` loop")
2214        .with_infer_code("break_value_in_non_loop")
2215        .with_span_label(&span, "`break` with value only allowed in `loop`")
2216        .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.")
2217}
2218
2219pub fn defer_in_loop(span: Span) -> LisetteDiagnostic {
2220    LisetteDiagnostic::error("`defer` inside loop")
2221        .with_infer_code("defer_in_loop")
2222        .with_span_label(&span, "not allowed inside loop")
2223        .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); }`")
2224}
2225
2226pub fn empty_range(span: &Span) -> LisetteDiagnostic {
2227    LisetteDiagnostic::error("Empty range")
2228        .with_infer_code("empty_range")
2229        .with_span_label(span, "start is greater than end")
2230        .with_help("Swap the bounds.")
2231}
2232
2233pub fn decimal_file_mode(span: &Span, value: u64) -> LisetteDiagnostic {
2234    LisetteDiagnostic::error("File permission written in decimal")
2235        .with_infer_code("decimal_file_mode")
2236        .with_span_label(span, format!("decimal {value} is octal 0o{value:o}"))
2237        .with_help(
2238            "File permissions are conventionally written in octal. Use a `0o` prefix (for example `0o644`) so the bits line up with the rwx triples.",
2239        )
2240}
2241
2242pub fn empty_infinite_loop(span: &Span) -> LisetteDiagnostic {
2243    LisetteDiagnostic::error("Empty infinite loop")
2244        .with_infer_code("empty_infinite_loop")
2245        .with_span_label(span, "busy-spins")
2246        .with_help(
2247            "An empty `loop` spins forever at 100% CPU. Block on a channel, call `time.Sleep`, or add a `break`.",
2248        )
2249}
2250
2251pub fn empty_select_default(span: &Span) -> LisetteDiagnostic {
2252    LisetteDiagnostic::error("Empty `select` default arm in a loop")
2253        .with_infer_code("empty_select_default")
2254        .with_span_label(span, "busy-spins")
2255        .with_help("Drop the `_ =>` arm to block, or do work in it.")
2256}
2257
2258pub fn repeated_if_condition(span: &Span) -> LisetteDiagnostic {
2259    LisetteDiagnostic::error("`if` condition repeated in `else if`")
2260        .with_infer_code("repeated_if_condition")
2261        .with_span_label(span, "same as prior condition")
2262        .with_help(
2263            "This branch is unreachable, because its condition duplicates the preceding condition. Did you mean a different condition?",
2264        )
2265}
2266
2267pub fn unchanging_loop_condition(span: &Span) -> LisetteDiagnostic {
2268    LisetteDiagnostic::error("Loop condition never changes")
2269        .with_infer_code("unchanging_loop_condition")
2270        .with_span_label(span, "never changes")
2271        .with_help(
2272            "Nothing in the loop body changes this condition, so the loop either never runs or never terminates. Did you forget to update a variable, or mean to `break` out of the loop?",
2273        )
2274}
2275
2276pub fn index_out_of_bounds(span: &Span, index_text: &str) -> LisetteDiagnostic {
2277    LisetteDiagnostic::error("Index out of bounds")
2278        .with_infer_code("index_out_of_bounds")
2279        .with_span_label(span, "out of bounds")
2280        .with_help(format!("Indexing at `{index_text}` will panic at runtime"))
2281}
2282
2283pub fn oversized_shift(
2284    span: &Span,
2285    type_name: &str,
2286    bit_width: u32,
2287    shift_amount: u64,
2288) -> LisetteDiagnostic {
2289    LisetteDiagnostic::error("Shift amount exceeds integer width")
2290        .with_infer_code("oversized_shift")
2291        .with_span_label(span, "shift exceeds width")
2292        .with_help(format!(
2293            "`{type_name}` is {bit_width} bits wide, so shifting by {shift_amount} discards every bit of the value."
2294        ))
2295}
2296
2297pub fn nan_comparison(span: &Span, always_true: bool) -> LisetteDiagnostic {
2298    let result = if always_true { "true" } else { "false" };
2299
2300    LisetteDiagnostic::error("Comparison with NaN")
2301        .with_infer_code("nan_comparison")
2302        .with_span_label(span, format!("always {result}"))
2303        .with_help(
2304            "NaN is unequal to every value including itself. Use `math.IsNaN(x)` to test for NaN.",
2305        )
2306}
2307
2308pub fn deferred_lock(span: Span, locked: &str, unlock: &str) -> LisetteDiagnostic {
2309    LisetteDiagnostic::error(format!("Deferred `{locked}` instead of `{unlock}`"))
2310        .with_infer_code("deferred_lock")
2311        .with_span_label(&span, "re-locks at function exit")
2312        .with_help(format!(
2313            "Deferring `{locked}` re-acquires the lock when the function returns, deadlocking the next caller. Did you mean `{unlock}`?"
2314        ))
2315}
2316
2317pub fn propagate_in_condition(span: Span) -> LisetteDiagnostic {
2318    LisetteDiagnostic::error("`?` cannot be used inside a condition")
2319        .with_infer_code("propagate_in_condition")
2320        .with_span_label(&span, "`?` inside condition")
2321        .with_help("Bind the result first: `let val = expression?; if val { ... }`")
2322}
2323
2324pub fn propagate_in_defer(span: Span) -> LisetteDiagnostic {
2325    LisetteDiagnostic::error("Invalid `?` in `defer`")
2326        .with_infer_code("propagate_in_defer")
2327        .with_span_label(&span, "`?` not allowed here")
2328        .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); } }`")
2329}
2330
2331pub fn return_in_defer_block(span: Span) -> LisetteDiagnostic {
2332    LisetteDiagnostic::error("`return` in `defer` block")
2333        .with_infer_code("return_in_defer_block")
2334        .with_span_label(&span, "not allowed inside `defer` block")
2335        .with_help("Remove the `return` as it only exits the `defer` block")
2336}
2337
2338pub fn break_in_defer_block(span: Span) -> LisetteDiagnostic {
2339    LisetteDiagnostic::error("`break` in `defer` block")
2340        .with_infer_code("break_in_defer_block")
2341        .with_span_label(&span, "not allowed inside `defer` block")
2342        .with_help("Remove the `break`, or move it inside a loop within the `defer` block")
2343}
2344
2345pub fn continue_in_defer_block(span: Span) -> LisetteDiagnostic {
2346    LisetteDiagnostic::error("`continue` in `defer` block")
2347        .with_infer_code("continue_in_defer_block")
2348        .with_span_label(&span, "not allowed inside `defer` block")
2349        .with_help("Remove the `continue`, or move it inside a loop within the `defer` block")
2350}
2351
2352pub fn invalid_cast(source_ty: &Type, target_ty: &Type, span: Span) -> LisetteDiagnostic {
2353    let same_constructor_with_unresolved = source_ty
2354        .get_qualified_id()
2355        .zip(target_ty.get_qualified_id())
2356        .is_some_and(|(s, t)| s == t)
2357        && source_ty.has_unbound_variables();
2358
2359    let help = if same_constructor_with_unresolved {
2360        format!(
2361            "Use a type annotation instead: `let x: {} = ...`",
2362            target_ty,
2363        )
2364    } else if source_ty.is_string() {
2365        "Strings cannot be cast to numbers and require explicit conversion. Use `strconv.Atoi()` to parse.".into()
2366    } else if source_ty.is_complex() || target_ty.is_complex() {
2367        "Complex numbers cannot be cast directly. Use `real(c)` or `imaginary(c)` to extract components.".into()
2368    } else if source_ty.has_underlying_rune() && target_ty.has_underlying_byte() {
2369        "rune (int32) is wider than byte (uint8) and may not fit. Use an intermediate variable to cast via int first: `let n = r as int; n as byte`".into()
2370    } else if source_ty.has_underlying_byte() && target_ty.is_string() {
2371        "A byte has two readings as a string. Use `[b] as string` to preserve the byte (may be invalid UTF-8), or cast through a rune to encode as a codepoint: `let r = b as rune; r as string`".into()
2372    } else {
2373        "Casts are supported between numeric types, between string and byte/rune slices, from rune to string, and from concrete types to interfaces.".into()
2374    };
2375
2376    LisetteDiagnostic::error("Invalid cast")
2377        .with_infer_code("invalid_cast")
2378        .with_span_label(
2379            &span,
2380            format!("cannot cast `{}` to `{}`", source_ty, target_ty),
2381        )
2382        .with_help(help)
2383}
2384
2385pub fn chained_cast(span: Span) -> LisetteDiagnostic {
2386    LisetteDiagnostic::error("Invalid cast")
2387        .with_infer_code("chained_cast")
2388        .with_span_label(&span, "chained cast not allowed")
2389        .with_help("Use an intermediate variable if you need to cast through multiple types")
2390}
2391
2392pub fn redundant_cast(ty: &Type, span: Span) -> LisetteDiagnostic {
2393    LisetteDiagnostic::info("Redundant cast")
2394        .with_infer_code("redundant_cast")
2395        .with_span_label(&span, format!("casting `{}` to itself has no effect", ty))
2396        .with_help("Remove the unnecessary cast")
2397}
2398
2399pub fn redundant_assert_type(ty: &Type, span: Span) -> LisetteDiagnostic {
2400    LisetteDiagnostic::info("Redundant type assertion")
2401        .with_infer_code("redundant_assert_type")
2402        .with_span_label(&span, format!("already of type `{}`", ty))
2403        .with_help(format!(
2404            "`assert_type` narrows an `Unknown` value, but this value is already `{}`, so the assertion always succeeds",
2405            ty
2406        ))
2407}
2408
2409pub fn integer_literal_overflow(
2410    target_ty: &str,
2411    min: i128,
2412    max: i128,
2413    span: Span,
2414) -> LisetteDiagnostic {
2415    LisetteDiagnostic::error("Integer literal overflow")
2416        .with_infer_code("integer_literal_overflow")
2417        .with_span_label(&span, format!("overflows `{}`", target_ty))
2418        .with_help(format!(
2419            "`{}` must be in range `{}` to `{}`",
2420            target_ty, min, max
2421        ))
2422}
2423
2424pub fn float_literal_overflow(target_ty: &str, span: Span) -> LisetteDiagnostic {
2425    LisetteDiagnostic::error("Float literal overflow")
2426        .with_infer_code("float_literal_overflow")
2427        .with_span_label(&span, format!("value overflows `{}`", target_ty))
2428        .with_help(format!(
2429            "Use `float64` for larger values, or ensure the value fits in `{}`",
2430            target_ty
2431        ))
2432}
2433
2434pub fn cannot_negate_unsigned(target_ty: &str, span: Span) -> LisetteDiagnostic {
2435    LisetteDiagnostic::error("Cannot negate unsigned type")
2436        .with_infer_code("cannot_negate_unsigned")
2437        .with_span_label(&span, format!("cannot negate `{}`", target_ty))
2438        .with_help("Unsigned types cannot represent negative values")
2439}
2440
2441fn go_builtin_hint(name: &str) -> Option<&'static str> {
2442    match name {
2443        "len" => Some(
2444            "Lisette has no `len` builtin. Use the `.length()` method instead, e.g. `items.length()`.",
2445        ),
2446        "cap" => Some(
2447            "Lisette has no `cap` builtin. Use the `.capacity()` method instead, e.g. `items.capacity()`.",
2448        ),
2449        "make" => Some(
2450            "Lisette has no `make` builtin. Use constructor methods instead, e.g. `Channel.new<int>()`, `Slice.new<int>()`, `Map.new<K, V>()`.",
2451        ),
2452        "append" => Some(
2453            "Lisette has no `append` builtin. Use the `.append()` method instead, e.g. `items.append(1)`.",
2454        ),
2455        "close" => Some(
2456            "Lisette has no `close` builtin. Use the `.close()` method instead, e.g. `ch.close()`.",
2457        ),
2458        "copy" => Some(
2459            "Lisette has no `copy` builtin. Use the `.copy_from()` method instead, e.g. `dst.copy_from(src)`.",
2460        ),
2461        "delete" => Some(
2462            "Lisette has no `delete` builtin. Use the `.delete()` method instead, e.g. `map.delete(key)`.",
2463        ),
2464        "new" => Some(
2465            "Lisette has no `new` builtin. Use constructor methods instead, e.g. `MyStruct { field: value }` or `MyType.new()`.",
2466        ),
2467        "print" | "println" | "printf" => Some(
2468            "Lisette has no `print` builtin. Use `fmt.Println`, `fmt.Printf`, etc. after `import \"go:fmt\"`.",
2469        ),
2470        _ => None,
2471    }
2472}
2473
2474pub fn levenshtein_distance(a: &str, b: &str) -> usize {
2475    let b_len = b.len();
2476
2477    if a.is_empty() {
2478        return b_len;
2479    }
2480    if b_len == 0 {
2481        return a.len();
2482    }
2483
2484    let mut prev: Vec<usize> = (0..=b_len).collect();
2485    let mut curr = vec![0; b_len + 1];
2486
2487    for (i, a_char) in a.chars().enumerate() {
2488        curr[0] = i + 1;
2489        for (j, b_char) in b.chars().enumerate() {
2490            let cost = if a_char == b_char { 0 } else { 1 };
2491            curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
2492        }
2493        std::mem::swap(&mut prev, &mut curr);
2494    }
2495
2496    prev[b_len]
2497}
2498
2499/// Uses Levenshtein distance (threshold <= 2) and prefix matching
2500/// to catch abbreviations like `len` → `length`.
2501pub fn find_similar_name(name: &str, candidates: &[String]) -> Option<String> {
2502    let best_distance = candidates
2503        .iter()
2504        .filter_map(|c| {
2505            let d = levenshtein_distance(name, c);
2506            (d <= 2).then_some((c, d))
2507        })
2508        .min_by_key(|(_, d)| *d);
2509
2510    let by_prefix = if name.len() >= 2 {
2511        candidates
2512            .iter()
2513            .filter(|c| c.starts_with(name) || name.starts_with(c.as_str()))
2514            .min_by_key(|c| c.len().abs_diff(name.len()))
2515    } else {
2516        None
2517    };
2518
2519    match (best_distance, by_prefix) {
2520        (Some((d, dist)), Some(p)) => {
2521            // Prefer Levenshtein only if it's a very close match (distance 1),
2522            // otherwise prefer prefix which better handles abbreviations
2523            if dist <= 1 {
2524                Some(d.clone())
2525            } else {
2526                Some(p.clone())
2527            }
2528        }
2529        (Some((d, _)), None) => Some(d.clone()),
2530        (None, Some(p)) => Some(p.clone()),
2531        (None, None) => None,
2532    }
2533}
2534
2535pub fn cannot_infer_type_argument(span: Span) -> LisetteDiagnostic {
2536    LisetteDiagnostic::error("Missing type argument")
2537        .with_infer_code("missing_type_argument")
2538        .with_span_label(&span, "expected type argument")
2539        .with_help("Supply a type argument for the call, e.g. `Channel.new<int>()`")
2540}
2541
2542fn format_list<T, F>(items: &[T], fmt: F) -> String
2543where
2544    F: Fn(&T) -> String,
2545{
2546    match items.len() {
2547        0 => String::new(),
2548        1 => fmt(&items[0]),
2549        2 => format!("{} and {}", fmt(&items[0]), fmt(&items[1])),
2550        _ => {
2551            let mut result = String::new();
2552            for (i, item) in items.iter().enumerate() {
2553                if i > 0 {
2554                    result.push_str(", ");
2555                }
2556                if i == items.len() - 1 {
2557                    result.push_str("and ");
2558                }
2559                result.push_str(&fmt(item));
2560            }
2561            result
2562        }
2563    }
2564}
2565
2566pub fn recursive_generic_instantiation(type_name: &str, span: Span) -> LisetteDiagnostic {
2567    LisetteDiagnostic::error("Recursive generic instantiation")
2568        .with_infer_code("recursive_instantiation")
2569        .with_span_label(&span, format!("`{}` is nested within itself", type_name))
2570        .with_help(format!(
2571            "Go does not allow recursive type instantiation (e.g., `{0}<{0}<T>>`). \
2572             Use a wrapper type or a different design.",
2573            type_name
2574        ))
2575}
2576
2577pub fn non_comparable_map_key(key_ty: &Type, reason: &str, span: Span) -> LisetteDiagnostic {
2578    LisetteDiagnostic::error("Invalid map key type")
2579        .with_infer_code("non_comparable_map_key")
2580        .with_span_label(&span, format!("`{}` is not comparable", key_ty))
2581        .with_help(format!(
2582            "Map keys must be comparable in Go. {} cannot be used as map keys.",
2583            reason
2584        ))
2585}
2586
2587pub fn ref_of_interface_type(inner_ty: &Type, span: Span) -> LisetteDiagnostic {
2588    LisetteDiagnostic::error("Invalid use of `Ref` with interface")
2589        .with_infer_code("ref_of_interface")
2590        .with_span_label(&span, "not allowed")
2591        .with_help(format!(
2592            "Use `{}` instead of `Ref<{}>`. Interfaces are already reference types in Go.",
2593            inner_ty, inner_ty
2594        ))
2595}
2596
2597pub fn float_modulo_not_supported(span: Span) -> LisetteDiagnostic {
2598    LisetteDiagnostic::error("Invalid operation")
2599        .with_infer_code("float_modulo")
2600        .with_span_label(&span, "`%` is not supported on floating-point types")
2601        .with_help("Use `math.Mod(x, y)` for floating-point modulo")
2602}
2603
2604pub fn recursive_type(type_name: &str, span: Span) -> LisetteDiagnostic {
2605    LisetteDiagnostic::error("Recursive type has infinite size")
2606        .with_infer_code("recursive_type")
2607        .with_span_label(
2608            &span,
2609            format!("`{}` contains itself without indirection", type_name),
2610        )
2611        .with_help(format!(
2612            "Use `Ref<{}>` for indirection. For example: `next: Option<Ref<{}>>`",
2613            type_name, type_name
2614        ))
2615}
2616
2617pub fn interface_self_embedding(interface_name: &str, span: Span) -> LisetteDiagnostic {
2618    LisetteDiagnostic::error("Recursive interface embedding")
2619        .with_infer_code("interface_cycle")
2620        .with_span_label(&span, format!("`{}` embeds itself", interface_name))
2621        .with_help("An interface cannot embed itself. Remove the self-referencing `impl`.")
2622}
2623
2624pub fn interface_embedding_cycle(cycle: &[String], span: Span) -> LisetteDiagnostic {
2625    let cycle_str = cycle.join(" → ");
2626    LisetteDiagnostic::error("Recursive interface embedding")
2627        .with_infer_code("interface_cycle")
2628        .with_span_label(&span, "creates a cycle")
2629        .with_help(format!(
2630            "Interface embedding cycle detected: {}. Break the cycle by removing one of the embeddings.",
2631            cycle_str
2632        ))
2633}
2634
2635pub fn interface_method_conflict(
2636    interface_name: &str,
2637    method_name: &str,
2638    parent1: &str,
2639    parent2: &str,
2640    span: Span,
2641) -> LisetteDiagnostic {
2642    LisetteDiagnostic::error("Conflicting method signatures")
2643        .with_infer_code("interface_method_conflict")
2644        .with_span_label(&span, format!("duplicate method `{}`", method_name))
2645        .with_help(format!(
2646            "Interface `{}` inherits conflicting definitions of `{}` from `{}` and `{}`. \
2647             Rename one of the methods or remove one of the embeddings.",
2648            interface_name, method_name, parent1, parent2
2649        ))
2650}
2651
2652pub fn impl_on_foreign_type(type_name: &str, module_name: &str, span: Span) -> LisetteDiagnostic {
2653    LisetteDiagnostic::error("Cannot implement methods on foreign type")
2654        .with_infer_code("impl_on_foreign_type")
2655        .with_span_label(
2656            &span,
2657            format!("`{}` is defined in module `{}`", type_name, module_name),
2658        )
2659        .with_help(format!(
2660            "Methods can only be defined on types in the same module. \
2661             Use a standalone function instead: `fn my_method(w: {}) {{ ... }}`",
2662            type_name
2663        ))
2664}
2665
2666pub fn impl_on_type_alias(_type_name: &str, span: Span) -> LisetteDiagnostic {
2667    LisetteDiagnostic::error("Cannot implement methods on type alias")
2668        .with_infer_code("impl_on_type_alias")
2669        .with_span_label(&span, "type alias")
2670        .with_help(
2671            "A type alias cannot carry its own methods. Either add methods to the underlying \
2672             type directly or define a tuple struct instead",
2673        )
2674}
2675
2676pub fn prelude_type_shadowed(name: &str, span: Span) -> LisetteDiagnostic {
2677    LisetteDiagnostic::error("Cannot shadow prelude type")
2678        .with_infer_code("prelude_type_shadowed")
2679        .with_span_label(&span, format!("`{}` is a prelude type", name))
2680        .with_help(format!(
2681            "Choose a different name — `{}` is defined in the prelude and cannot be redefined",
2682            name
2683        ))
2684}
2685
2686pub fn prelude_function_shadowed(name: &str, span: Span) -> LisetteDiagnostic {
2687    LisetteDiagnostic::error("Cannot shadow prelude function")
2688        .with_infer_code("prelude_function_shadowed")
2689        .with_span_label(&span, format!("`{}` is a prelude function", name))
2690        .with_help(format!(
2691            "Choose a different name — `{}` is defined in the prelude and cannot be redefined",
2692            name
2693        ))
2694}
2695
2696pub fn pub_type_not_exportable(name: &str, suggested: &str, span: Span) -> LisetteDiagnostic {
2697    LisetteDiagnostic::error("Public type is not exportable")
2698        .with_infer_code("pub_type_not_exportable")
2699        .with_span_label(&span, format!("`{}` cannot be exported from Go", name))
2700        .with_help(format!(
2701            "Public types become exported Go identifiers, which must start with an uppercase letter. Rename to `{}`",
2702            suggested
2703        ))
2704}
2705
2706pub fn predeclared_type_shadowed(name: &str, span: Span) -> LisetteDiagnostic {
2707    LisetteDiagnostic::error("Cannot shadow Go predeclared identifier")
2708        .with_infer_code("predeclared_type_shadowed")
2709        .with_span_label(&span, format!("`{}` is a Go predeclared identifier", name))
2710        .with_help(format!(
2711            "Lisette emits `{}` directly in the generated Go, so a user declaration with this name shadows the builtin and the output fails to compile. Choose a different name",
2712            name
2713        ))
2714}
2715
2716pub fn non_pub_interface_with_pub_impl(
2717    interface_name: &str,
2718    struct_name: &str,
2719    span: Span,
2720) -> LisetteDiagnostic {
2721    LisetteDiagnostic::error("Visibility mismatch in interface implementation")
2722        .with_infer_code("non_pub_interface_pub_impl")
2723        .with_span_label(
2724            &span,
2725            "has public methods, but interface is private",
2726        )
2727        .with_help(format!(
2728            "`{}` implements public methods for the private interface `{}`. Either make the interface `pub`, or remove `pub` from the struct methods",
2729            struct_name, interface_name
2730        ))
2731}
2732
2733pub fn missing_constraint_on_generic_return_type(
2734    fn_name: &str,
2735    param_name: &str,
2736    constraint: &Type,
2737    span: Span,
2738) -> LisetteDiagnostic {
2739    LisetteDiagnostic::error("Missing constraint on generic return type")
2740        .with_infer_code("missing_constraint_on_return_type")
2741        .with_span_label(
2742            &span,
2743            format!("expected `{}` to be constrained", param_name),
2744        )
2745        .with_help(
2746            format!(
2747                "Constrain the generic: `{}<{}: {}>()`",
2748                fn_name, param_name, constraint
2749            ) + ". The function returns a type whose methods depend on the constraint",
2750        )
2751}
2752
2753pub fn panic_in_expression_position(span: Span) -> LisetteDiagnostic {
2754    LisetteDiagnostic::error("`panic()` used as a value")
2755        .with_infer_code("panic_in_expression_position")
2756        .with_span_label(&span, "disallowed")
2757        .with_help("`panic()` can only be used in statement position, not assigned to a variable or passed as an argument")
2758}
2759
2760pub fn specialized_impl_cannot_satisfy_interface(
2761    struct_name: &str,
2762    interface_name: &str,
2763    method_name: &str,
2764    generics: &[String],
2765    span: Span,
2766) -> LisetteDiagnostic {
2767    let params = generics.join(", ");
2768    LisetteDiagnostic::error("Specialized impl cannot satisfy interface")
2769        .with_infer_code("specialized_impl_cannot_satisfy_interface")
2770        .with_span_label(
2771            &span,
2772            format!(
2773                "`{}` on `{}` cannot satisfy `{}`",
2774                method_name, struct_name, interface_name
2775            ),
2776        )
2777        .with_help(format!(
2778            "Methods in specialized `impl` blocks cannot satisfy interfaces. \
2779             Move `{}` to a generic `impl` block: `impl<{params}> {}<{params}> {{}}`",
2780            method_name, struct_name
2781        ))
2782}
2783
2784pub enum NativeMethodForm {
2785    Instance,
2786    Static,
2787}
2788
2789pub fn native_method_value(method: &str, form: NativeMethodForm, span: Span) -> LisetteDiagnostic {
2790    let help = match form {
2791        NativeMethodForm::Instance => format!(
2792            "Call it directly: `receiver.{method}()`. To use it as a value, wrap in a closure: `|args| receiver.{method}(args)`"
2793        ),
2794        NativeMethodForm::Static => {
2795            format!("Use a closure instead: `|args| receiver.{method}(args)`")
2796        }
2797    };
2798    LisetteDiagnostic::error("Cannot use native method as a value")
2799        .with_infer_code("native_method_value")
2800        .with_span_label(&span, "native methods must be called directly")
2801        .with_help(help)
2802}
2803
2804pub fn native_constructor_value(name: &str, span: Span) -> LisetteDiagnostic {
2805    LisetteDiagnostic::error("Cannot use native constructor as a value")
2806        .with_infer_code("native_constructor_value")
2807        .with_span_label(&span, "native constructors must be called directly")
2808        .with_help(format!("Use a closure instead: `|args| {name}(args)`"))
2809}
2810
2811pub fn enum_variant_constructor_value(name: &str, span: Span) -> LisetteDiagnostic {
2812    LisetteDiagnostic::error("Cannot use enum variant as value")
2813        .with_infer_code("enum_variant_constructor_value")
2814        .with_span_label(&span, "used as value")
2815        .with_help(format!(
2816            "Instantiate the variant: `{name} {{ field: value, ... }}`"
2817        ))
2818}
2819
2820pub fn record_struct_value(name: &str, span: Span) -> LisetteDiagnostic {
2821    LisetteDiagnostic::error("Cannot use struct type as a value")
2822        .with_infer_code("record_struct_value")
2823        .with_span_label(&span, "struct types cannot be used as expressions")
2824        .with_help(format!(
2825            "Use a struct literal instead: `{name} {{ field: value, ... }}`"
2826        ))
2827}
2828
2829pub fn namespace_alias_used_as_value(span: Span) -> LisetteDiagnostic {
2830    LisetteDiagnostic::error("Cannot use a module or enum-type alias as a value")
2831        .with_infer_code("namespace_alias_used_as_value")
2832        .with_span_label(
2833            &span,
2834            "this alias refers to a type or module, not a runtime value",
2835        )
2836        .with_help("Access a member instead, e.g. `alias.VariantName`")
2837}
2838
2839pub fn let_binding_module_namespace(module_name: &str, span: Span) -> LisetteDiagnostic {
2840    LisetteDiagnostic::error("Cannot bind a module namespace to a variable")
2841        .with_infer_code("let_binding_module_namespace")
2842        .with_span_label(&span, "module namespaces are not runtime values")
2843        .with_help(format!(
2844            "Use an import alias instead: `import alias \"{module_name}\"`"
2845        ))
2846}
2847
2848pub fn let_binding_enum_type(type_name: &str, span: Span) -> LisetteDiagnostic {
2849    LisetteDiagnostic::error("Cannot bind an enum type to a variable")
2850        .with_infer_code("let_binding_enum_type")
2851        .with_span_label(&span, "enum types are not runtime values")
2852        .with_help(format!(
2853            "Use a type alias instead: `type Alias = {type_name}`"
2854        ))
2855}
2856
2857pub fn private_method_expression(span: Span) -> LisetteDiagnostic {
2858    LisetteDiagnostic::error("Cannot use private method as a value")
2859        .with_infer_code("private_method_expression")
2860        .with_span_label(&span, "private methods must be called directly")
2861        .with_help("Use a closure instead: `|self_, args| self_.method(args)`")
2862}
2863
2864pub fn float_literal_int_cast(span: Span) -> LisetteDiagnostic {
2865    LisetteDiagnostic::error("Cannot cast float literal to integer directly")
2866        .with_infer_code("float_literal_int_cast")
2867        .with_span_label(&span, "unsupported cast")
2868        .with_help("Bind to a variable first: `let f = 1.0; f as int`")
2869}
2870
2871pub fn const_requires_simple_expression(span: Span) -> LisetteDiagnostic {
2872    LisetteDiagnostic::error("`const` requires a simple expression")
2873        .with_infer_code("const_requires_simple_expression")
2874        .with_span_label(&span, "expected literal or simple expression")
2875        .with_help("Use `let` for computed values")
2876}
2877
2878pub fn complex_sub_expression(span: Span) -> LisetteDiagnostic {
2879    LisetteDiagnostic::error("Complex expression used as sub-expression")
2880        .with_infer_code("complex_sub_expression")
2881        .with_span_label(&span, "expected simple expression")
2882        .with_help("Hoist to a `let` binding")
2883}
2884
2885pub fn reference_through_newtype(span: Span) -> LisetteDiagnostic {
2886    LisetteDiagnostic::error("Cannot take reference through newtype boundary")
2887        .with_infer_code("reference_through_newtype")
2888        .with_span_label(&span, "newtype `.0` inside `&`")
2889        .with_help("Bind the inner value first: `let inner = val.0; &inner`")
2890}
2891
2892pub fn immutable_argument_to_mut_param(
2893    var_name: &str,
2894    callee_label: &str,
2895    span: Span,
2896) -> LisetteDiagnostic {
2897    let help = format!(
2898        "{callee_label} may mutate `{var_name}`, so declare it mutable using `let mut {var_name} = ...`."
2899    );
2900    LisetteDiagnostic::error("Immutable argument passed to `mut` parameter")
2901        .with_infer_code("immutable_arg_to_mut_param")
2902        .with_span_label(&span, "expected mutable, found immutable")
2903        .with_help(help)
2904}
2905
2906pub fn failure_propagation_in_expression(span: Span) -> LisetteDiagnostic {
2907    LisetteDiagnostic::error("Failure propagation in expression position")
2908        .with_infer_code("failure_propagation_in_expression")
2909        .with_span_label(
2910            &span,
2911            "`Err(..)?` and `None?` always early-return and never produce a value",
2912        )
2913        .with_help("Use `return Err(..)` or `return None` instead")
2914}
2915
2916pub fn never_call_in_expression(span: Span) -> LisetteDiagnostic {
2917    LisetteDiagnostic::error("Never-returning call in expression position")
2918        .with_infer_code("never_call_in_expression")
2919        .with_span_label(&span, "`panic` never returns and cannot produce a value")
2920        .with_help("Use `panic(...)` as a statement instead")
2921}
2922
2923pub fn invalid_main_signature(span: Span) -> LisetteDiagnostic {
2924    LisetteDiagnostic::error("Invalid main signature")
2925        .with_infer_code("invalid_main_signature")
2926        .with_span_label(&span, "`main` must have no parameters and no return type")
2927        .with_help(
2928            "Use `fn main() { ... }`. To handle errors, use `match` or `if let` \
2929             inside main instead of returning `Result`.",
2930        )
2931}
2932
2933pub fn parenthesized_qualifier(path: &str, member: &str, span: Span) -> LisetteDiagnostic {
2934    LisetteDiagnostic::error("Unnecessary parentheses around qualifier")
2935        .with_infer_code("parenthesized_qualifier")
2936        .with_span_label(&span, "parenthesized qualifier")
2937        .with_help(format!("Remove the parentheses: `{}.{}`", path, member))
2938}
2939
2940pub fn type_alias_as_qualifier(
2941    alias: &str,
2942    underlying: &str,
2943    member: &str,
2944    span: Span,
2945) -> LisetteDiagnostic {
2946    LisetteDiagnostic::error("Cannot use generic type alias as qualifier")
2947        .with_infer_code("type_alias_as_qualifier")
2948        .with_span_label(
2949            &span,
2950            format!("`{}` aliases `{}`", alias, underlying),
2951        )
2952        .with_help(format!(
2953            "Aliases for types with generic parameters are not supported as qualifiers. Use the original type directly: `{}.{}`",
2954            underlying, member
2955        ))
2956}
2957
2958pub fn ref_qualifier(member: &str, span: Span) -> LisetteDiagnostic {
2959    LisetteDiagnostic::error("Invalid `Ref` construction")
2960        .with_infer_code("ref_qualifier")
2961        .with_span_label(&span, format!("`Ref` has no `{}`", member))
2962        .with_help("To take a reference, use `&value`")
2963}
2964
2965pub fn control_flow_in_expression(keyword: &str, span: Span) -> LisetteDiagnostic {
2966    LisetteDiagnostic::error(format!(
2967        "`{}` cannot be used in expression position",
2968        keyword
2969    ))
2970    .with_infer_code("control_flow_in_expression")
2971    .with_span_label(
2972        &span,
2973        format!("`{}` is a statement and cannot produce a value", keyword),
2974    )
2975    .with_help(format!(
2976        "Use `{}` as a standalone statement instead",
2977        keyword
2978    ))
2979}
2980
2981pub fn spread_on_non_variadic(span: Span) -> LisetteDiagnostic {
2982    LisetteDiagnostic::error("Invalid spread argument")
2983        .with_infer_code("spread_on_non_variadic")
2984        .with_span_label(&span, "this function does not accept variadic arguments")
2985        .with_help("Only functions with a `VarArgs<T>` parameter accept a `xs...` spread")
2986}
2987
2988pub fn range_to_for_variadic(span: Span, var_name: Option<&str>) -> LisetteDiagnostic {
2989    let suggestion = match var_name {
2990        Some(name) => format!("Use postfix: `{}...`", name),
2991        None => "Use postfix `...` for variadic spread".to_string(),
2992    };
2993    LisetteDiagnostic::error("Invalid range argument")
2994        .with_infer_code("range_to_for_variadic")
2995        .with_span_label(&span, "this is a range, not a spread")
2996        .with_help(suggestion)
2997}
2998
2999pub fn reference_aliases_sibling(ref_span: Span, var_name: &str) -> LisetteDiagnostic {
3000    LisetteDiagnostic::error("Reference aliases sibling expression")
3001        .with_infer_code("reference_aliases_sibling")
3002        .with_span_label(
3003            &ref_span,
3004            format!(
3005                "`&{}` could mutate `{}` used by a sibling",
3006                var_name, var_name
3007            ),
3008        )
3009        .with_help(format!(
3010            "Bind `{}` to a `let` before this expression to make evaluation order explicit",
3011            var_name
3012        ))
3013}