1use crate::span::Span;
26use crate::types::{Type, TypeVar};
27use std::fmt;
28
29#[derive(Debug, Clone)]
31pub struct TypeError {
32 pub kind: TypeErrorKind,
34 pub span: Option<Span>,
36 pub context: Vec<String>,
38}
39
40#[derive(Debug, Clone)]
42pub enum TypeErrorKind {
43 Mismatch {
45 expected: Type,
47 got: Type,
49 },
50
51 OccursCheck {
53 var: TypeVar,
55 in_type: Type,
57 },
58
59 UnboundVariable {
61 name: String,
63 },
64
65 FieldNotFound {
67 record_type: Type,
69 field: String,
71 },
72
73 ArityMismatch {
75 expected: usize,
77 got: usize,
79 },
80
81 NotAFunction {
83 got: Type,
85 },
86
87 PatternMismatch {
89 pattern_type: Type,
91 scrutinee_type: Type,
93 },
94
95 NotATuple {
97 got: Type,
99 },
100
101 TupleIndexOutOfBounds {
103 tuple_type: Type,
105 index: usize,
107 size: usize,
109 },
110
111 NotAList {
113 got: Type,
115 },
116
117 NotAnArray {
119 got: Type,
121 },
122
123 NotARecord {
125 got: Type,
127 },
128
129 DuplicateField {
131 field: String,
133 },
134
135 MissingField {
137 record_type: Type,
139 field: String,
141 },
142
143 Custom {
145 message: String,
147 },
148}
149
150impl TypeError {
151 pub fn new(kind: TypeErrorKind) -> Self {
153 TypeError {
154 kind,
155 span: None,
156 context: Vec::new(),
157 }
158 }
159
160 pub fn with_span(kind: TypeErrorKind, span: Span) -> Self {
162 TypeError {
163 kind,
164 span: Some(span),
165 context: Vec::new(),
166 }
167 }
168
169 pub fn add_context(&mut self, ctx: String) {
171 self.context.push(ctx);
172 }
173
174 pub fn with_context(mut self, ctx: String) -> Self {
176 self.add_context(ctx);
177 self
178 }
179
180 pub fn format(&self, source: &str) -> String {
189 let mut output = String::new();
190
191 output.push_str(&format!("Error: {}\n", self));
193
194 if let Some(span) = &self.span {
196 output.push_str(&format!(" --> {}\n", span.format_location()));
197
198 if let Some(highlight) = self.format_source_highlight(source, span) {
200 output.push_str(&highlight);
201 }
202 }
203
204 if !self.context.is_empty() {
206 output.push('\n');
207 for ctx in &self.context {
208 output.push_str(&format!(" in {}\n", ctx));
209 }
210 }
211
212 if let Some(suggestion) = self.suggest_fix() {
214 output.push_str(&format!("\n Help: {}\n", suggestion));
215 }
216
217 output
218 }
219
220 fn format_source_highlight(&self, source: &str, span: &Span) -> Option<String> {
222 let lines: Vec<&str> = source.lines().collect();
223 if span.start.line == 0 || span.start.line > lines.len() {
224 return None;
225 }
226
227 let line_idx = span.start.line - 1;
228 let line = lines[line_idx];
229
230 let mut output = String::new();
231 output.push_str(" |\n");
232 output.push_str(&format!("{:3} | {}\n", span.start.line, line));
233 output.push_str(" | ");
234
235 let start_col = span.start.column.saturating_sub(1);
237 let end_col = if span.is_single_line() {
238 span.end.column.saturating_sub(1)
239 } else {
240 line.len()
241 };
242
243 for i in 0..line.len() {
244 if i >= start_col && i < end_col {
245 output.push('^');
246 } else {
247 output.push(' ');
248 }
249 }
250 output.push('\n');
251
252 Some(output)
253 }
254
255 pub fn suggest_fix(&self) -> Option<String> {
257 match &self.kind {
258 TypeErrorKind::UnboundVariable { name } => {
259 Some(format!("Did you forget to define '{}'?", name))
260 }
261 TypeErrorKind::Mismatch { expected, got } => {
262 match (expected, got) {
264 (Type::Int, Type::String) | (Type::Int, Type::Float) => {
265 Some("Try using a numeric conversion function".to_string())
266 }
267 (Type::String, Type::Int) | (Type::String, Type::Float) => {
268 Some("Try using string conversion or formatting".to_string())
269 }
270 (Type::List(_), Type::Array(_)) | (Type::Array(_), Type::List(_)) => {
271 Some("Lists and arrays are different types - use appropriate conversion functions".to_string())
272 }
273 (Type::Function(_, _), _) => {
274 Some("Did you forget to apply all function arguments?".to_string())
275 }
276 (_, Type::Function(_, _)) => {
277 Some("Did you provide too many arguments?".to_string())
278 }
279 _ => None,
280 }
281 }
282 TypeErrorKind::NotAFunction { got } => match got {
283 Type::Int | Type::Bool | Type::String | Type::Unit | Type::Float => {
284 Some("This is a value, not a function - did you mean to call a function instead?".to_string())
285 }
286 _ => Some("This expression is not a function and cannot be called".to_string()),
287 },
288 TypeErrorKind::FieldNotFound { record_type, field: _ } => {
289 if let Type::Record(fields) = record_type {
290 let available: Vec<_> = fields.keys().map(|s| s.as_str()).collect();
291 Some(format!(
292 "Available fields are: {}. Did you mean one of these?",
293 available.join(", ")
294 ))
295 } else {
296 None
297 }
298 }
299 TypeErrorKind::ArityMismatch { expected, got } => {
300 if got < expected {
301 Some(format!("You provided {} arguments but {} are required", got, expected))
302 } else {
303 Some(format!("You provided {} arguments but only {} are expected", got, expected))
304 }
305 }
306 TypeErrorKind::TupleIndexOutOfBounds { size, index, .. } => {
307 Some(format!(
308 "Tuple has {} elements (valid indices: 0-{}), but you tried to access index {}",
309 size,
310 size - 1,
311 index
312 ))
313 }
314 TypeErrorKind::NotATuple { .. } => {
315 Some("Use tuple syntax (x, y, z) to create tuples".to_string())
316 }
317 TypeErrorKind::NotAList { .. } => {
318 Some("Use list syntax [x; y; z] to create lists".to_string())
319 }
320 TypeErrorKind::NotAnArray { .. } => {
321 Some("Use array syntax [|x; y; z|] to create arrays".to_string())
322 }
323 TypeErrorKind::NotARecord { .. } => {
324 Some("Use record syntax { field = value } to create records".to_string())
325 }
326 TypeErrorKind::DuplicateField { field } => {
327 Some(format!("Remove the duplicate definition of field '{}'", field))
328 }
329 TypeErrorKind::MissingField { field, .. } => {
330 Some(format!("Add the required field '{}' to the record", field))
331 }
332 TypeErrorKind::OccursCheck { var, in_type } => {
333 Some(format!(
334 "This would create an infinite type {} = {}. Check your recursive type definitions.",
335 var, in_type
336 ))
337 }
338 TypeErrorKind::PatternMismatch { .. } => {
339 Some("Pattern type must match the type of the value being matched".to_string())
340 }
341 TypeErrorKind::Custom { .. } => None,
342 }
343 }
344}
345
346impl fmt::Display for TypeError {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 match &self.kind {
349 TypeErrorKind::Mismatch { expected, got } => {
350 writeln!(f, "Type mismatch")?;
351 writeln!(f, " Expected: {}", expected)?;
352 write!(f, " Got: {}", got)
353 }
354 TypeErrorKind::OccursCheck { var, in_type } => {
355 writeln!(f, "Occurs check failed")?;
356 write!(f, " Cannot construct infinite type {} = {}", var, in_type)
357 }
358 TypeErrorKind::UnboundVariable { name } => {
359 write!(f, "Unbound variable: {}", name)
360 }
361 TypeErrorKind::FieldNotFound { record_type, field } => {
362 write!(
363 f,
364 "Field '{}' not found in record type {}",
365 field, record_type
366 )
367 }
368 TypeErrorKind::ArityMismatch { expected, got } => {
369 write!(
370 f,
371 "Arity mismatch: expected {} arguments, got {}",
372 expected, got
373 )
374 }
375 TypeErrorKind::NotAFunction { got } => {
376 write!(f, "Not a function: cannot call value of type {}", got)
377 }
378 TypeErrorKind::PatternMismatch {
379 pattern_type,
380 scrutinee_type,
381 } => {
382 writeln!(f, "Pattern match type mismatch")?;
383 writeln!(f, " Pattern type: {}", pattern_type)?;
384 write!(f, " Scrutinee type: {}", scrutinee_type)
385 }
386 TypeErrorKind::NotATuple { got } => {
387 write!(
388 f,
389 "Not a tuple: cannot access tuple element of type {}",
390 got
391 )
392 }
393 TypeErrorKind::TupleIndexOutOfBounds {
394 tuple_type,
395 index,
396 size,
397 } => {
398 write!(
399 f,
400 "Tuple index out of bounds: type {} has {} elements, but index {} was accessed",
401 tuple_type, size, index
402 )
403 }
404 TypeErrorKind::NotAList { got } => {
405 write!(
406 f,
407 "Not a list: cannot perform list operation on type {}",
408 got
409 )
410 }
411 TypeErrorKind::NotAnArray { got } => {
412 write!(
413 f,
414 "Not an array: cannot perform array operation on type {}",
415 got
416 )
417 }
418 TypeErrorKind::NotARecord { got } => {
419 write!(f, "Not a record: cannot access field of type {}", got)
420 }
421 TypeErrorKind::DuplicateField { field } => {
422 write!(f, "Duplicate field in record: '{}'", field)
423 }
424 TypeErrorKind::MissingField { record_type, field } => {
425 write!(
426 f,
427 "Missing field '{}' in record type {}",
428 field, record_type
429 )
430 }
431 TypeErrorKind::Custom { message } => {
432 write!(f, "{}", message)
433 }
434 }
435 }
436}
437
438impl std::error::Error for TypeError {}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use crate::span::Position;
444 use crate::types::TypeVar;
445
446 #[test]
451 fn test_type_error_new() {
452 let err = TypeError::new(TypeErrorKind::UnboundVariable {
453 name: "x".to_string(),
454 });
455 assert!(err.span.is_none());
456 assert!(err.context.is_empty());
457 }
458
459 #[test]
460 fn test_type_error_with_span() {
461 let span = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
462 let err = TypeError::with_span(
463 TypeErrorKind::UnboundVariable {
464 name: "x".to_string(),
465 },
466 span,
467 );
468 assert_eq!(err.span, Some(span));
469 }
470
471 #[test]
472 fn test_type_error_add_context() {
473 let mut err = TypeError::new(TypeErrorKind::UnboundVariable {
474 name: "x".to_string(),
475 });
476 err.add_context("function application".to_string());
477 err.add_context("let binding".to_string());
478 assert_eq!(err.context.len(), 2);
479 assert_eq!(err.context[0], "function application");
480 assert_eq!(err.context[1], "let binding");
481 }
482
483 #[test]
484 fn test_type_error_with_context_builder() {
485 let err = TypeError::new(TypeErrorKind::UnboundVariable {
486 name: "x".to_string(),
487 })
488 .with_context("function application".to_string());
489 assert_eq!(err.context.len(), 1);
490 }
491
492 #[test]
497 fn test_display_type_mismatch() {
498 let err = TypeError::new(TypeErrorKind::Mismatch {
499 expected: Type::Int,
500 got: Type::String,
501 });
502 let display = format!("{}", err);
503 assert!(display.contains("Type mismatch"));
504 assert!(display.contains("Expected: int"));
505 assert!(display.contains("Got: string"));
506 }
507
508 #[test]
509 fn test_display_occurs_check() {
510 let var = TypeVar::new(0, "a");
511 let err = TypeError::new(TypeErrorKind::OccursCheck {
512 var: var.clone(),
513 in_type: Type::List(Box::new(Type::Var(var))),
514 });
515 let display = format!("{}", err);
516 assert!(display.contains("Occurs check failed"));
517 assert!(display.contains("infinite type"));
518 }
519
520 #[test]
521 fn test_display_unbound_variable() {
522 let err = TypeError::new(TypeErrorKind::UnboundVariable {
523 name: "foo".to_string(),
524 });
525 let display = format!("{}", err);
526 assert!(display.contains("Unbound variable: foo"));
527 }
528
529 #[test]
530 fn test_display_field_not_found() {
531 use std::collections::HashMap;
532 let mut fields = HashMap::new();
533 fields.insert("x".to_string(), Type::Int);
534 let record = Type::Record(fields);
535
536 let err = TypeError::new(TypeErrorKind::FieldNotFound {
537 record_type: record,
538 field: "y".to_string(),
539 });
540 let display = format!("{}", err);
541 assert!(display.contains("Field 'y' not found"));
542 }
543
544 #[test]
545 fn test_display_arity_mismatch() {
546 let err = TypeError::new(TypeErrorKind::ArityMismatch {
547 expected: 2,
548 got: 3,
549 });
550 let display = format!("{}", err);
551 assert!(display.contains("Arity mismatch"));
552 assert!(display.contains("expected 2"));
553 assert!(display.contains("got 3"));
554 }
555
556 #[test]
557 fn test_display_not_a_function() {
558 let err = TypeError::new(TypeErrorKind::NotAFunction { got: Type::Int });
559 let display = format!("{}", err);
560 assert!(display.contains("Not a function"));
561 assert!(display.contains("int"));
562 }
563
564 #[test]
565 fn test_display_pattern_mismatch() {
566 let err = TypeError::new(TypeErrorKind::PatternMismatch {
567 pattern_type: Type::Int,
568 scrutinee_type: Type::String,
569 });
570 let display = format!("{}", err);
571 assert!(display.contains("Pattern match type mismatch"));
572 }
573
574 #[test]
575 fn test_display_not_a_tuple() {
576 let err = TypeError::new(TypeErrorKind::NotATuple { got: Type::Int });
577 let display = format!("{}", err);
578 assert!(display.contains("Not a tuple"));
579 }
580
581 #[test]
582 fn test_display_tuple_index_out_of_bounds() {
583 let tuple = Type::Tuple(vec![Type::Int, Type::Bool]);
584 let err = TypeError::new(TypeErrorKind::TupleIndexOutOfBounds {
585 tuple_type: tuple,
586 index: 3,
587 size: 2,
588 });
589 let display = format!("{}", err);
590 assert!(display.contains("Tuple index out of bounds"));
591 assert!(display.contains("index 3"));
592 }
593
594 #[test]
595 fn test_display_not_a_list() {
596 let err = TypeError::new(TypeErrorKind::NotAList { got: Type::Int });
597 let display = format!("{}", err);
598 assert!(display.contains("Not a list"));
599 }
600
601 #[test]
602 fn test_display_not_an_array() {
603 let err = TypeError::new(TypeErrorKind::NotAnArray { got: Type::Int });
604 let display = format!("{}", err);
605 assert!(display.contains("Not an array"));
606 }
607
608 #[test]
609 fn test_display_duplicate_field() {
610 let err = TypeError::new(TypeErrorKind::DuplicateField {
611 field: "name".to_string(),
612 });
613 let display = format!("{}", err);
614 assert!(display.contains("Duplicate field"));
615 assert!(display.contains("name"));
616 }
617
618 #[test]
619 fn test_display_custom() {
620 let err = TypeError::new(TypeErrorKind::Custom {
621 message: "Something went wrong".to_string(),
622 });
623 let display = format!("{}", err);
624 assert_eq!(display, "Something went wrong");
625 }
626
627 #[test]
632 fn test_suggestion_unbound_variable() {
633 let err = TypeError::new(TypeErrorKind::UnboundVariable {
634 name: "foo".to_string(),
635 });
636 let suggestion = err.suggest_fix();
637 assert!(suggestion.is_some());
638 assert!(suggestion.unwrap().contains("Did you forget to define"));
639 }
640
641 #[test]
642 fn test_suggestion_int_to_string_mismatch() {
643 let err = TypeError::new(TypeErrorKind::Mismatch {
644 expected: Type::Int,
645 got: Type::String,
646 });
647 let suggestion = err.suggest_fix();
648 assert!(suggestion.is_some());
649 assert!(suggestion.unwrap().contains("conversion"));
650 }
651
652 #[test]
653 fn test_suggestion_not_a_function() {
654 let err = TypeError::new(TypeErrorKind::NotAFunction { got: Type::Int });
655 let suggestion = err.suggest_fix();
656 assert!(suggestion.is_some());
657 }
658
659 #[test]
664 fn test_format_with_source_simple() {
665 let source = "let x = 42";
666 let span = Span::new(Position::new(1, 5, 4), Position::new(1, 6, 5));
667 let err = TypeError::with_span(
668 TypeErrorKind::UnboundVariable {
669 name: "x".to_string(),
670 },
671 span,
672 );
673 let formatted = err.format(source);
674 assert!(formatted.contains("Error:"));
675 assert!(formatted.contains("line 1, column 5"));
676 assert!(formatted.contains("let x = 42"));
677 }
678
679 #[test]
680 fn test_format_with_context() {
681 let source = "let x = 42";
682 let span = Span::new(Position::new(1, 1, 0), Position::new(1, 3, 2));
683 let err = TypeError::with_span(
684 TypeErrorKind::Mismatch {
685 expected: Type::Int,
686 got: Type::String,
687 },
688 span,
689 )
690 .with_context("function application".to_string());
691
692 let formatted = err.format(source);
693 assert!(formatted.contains("in function application"));
694 }
695
696 #[test]
697 fn test_format_with_suggestion() {
698 let source = "let x = y";
699 let span = Span::new(Position::new(1, 9, 8), Position::new(1, 10, 9));
700 let err = TypeError::with_span(
701 TypeErrorKind::UnboundVariable {
702 name: "y".to_string(),
703 },
704 span,
705 );
706 let formatted = err.format(source);
707 assert!(formatted.contains("Help:"));
708 assert!(formatted.contains("Did you forget to define"));
709 }
710
711 #[test]
712 fn test_format_source_highlight() {
713 let source = "let x = 42";
714 let span = Span::new(Position::new(1, 9, 8), Position::new(1, 11, 10));
715 let err = TypeError::with_span(
716 TypeErrorKind::Mismatch {
717 expected: Type::String,
718 got: Type::Int,
719 },
720 span,
721 );
722 let formatted = err.format(source);
723 assert!(formatted.contains("let x = 42"));
724 assert!(formatted.contains("^^"));
725 }
726}