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