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