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