Skip to main content

lisette_diagnostics/
infer.rs

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