Skip to main content

lisette_diagnostics/
infer.rs

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