1use std::{
36 borrow::Cow,
37 fmt::{Display, Formatter},
38 ops::Range,
39};
40
41use compact_str::CompactString;
42use lady_deirdre::{
43 format::AnnotationPriority,
44 syntax::{ErrorRef, NodeRef, NodeRule, RecoveryResult},
45};
46
47use crate::{
48 analysis::DiagnosticsDepth,
49 runtime::{ops::OperatorKind, ScriptOrigin, ScriptType, TypeFamily, TypeHint, TypeMeta},
50 syntax::{PolyRefOrigin, ScriptDoc, ScriptNode, SpanBounds},
51};
52
53#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
70#[repr(u16)]
71#[non_exhaustive]
72pub enum IssueCode {
73 Parse = 101,
79
80 UnresolvedPackage = 201,
85 NotAPackage = 202,
90 OrphanedBreak = 203,
94 DuplicateParam = 204,
99 ReadUninit = 205,
104 UnresolvedIdent = 206,
109 IntParse = 207,
113 FloatParse = 208,
117 UnreachableStatement = 209,
123 UnreachableArm = 210,
129 DuplicateEntry = 211,
133 LiteralAssignment = 212,
138
139 TypeMismatch = 301,
144 NilIndex = 302,
148 IndexTypeMismatch = 303,
153 UndefinedOperator = 304,
157 UndefinedDisplay = 305,
161 CallArityMismatch = 306,
165 FnArityMismatch = 307,
169 ResultMismatch = 308,
173 UnknownComponent = 309,
177 InconsistentReturns = 310,
183}
184
185impl Display for IssueCode {
186 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
187 let message = match self {
188 Self::Parse => "Parse error.",
189
190 Self::UnresolvedPackage => "Unresolved import.",
191 Self::NotAPackage => "Importing a component that is not a package.",
192 Self::OrphanedBreak => "Break outside of a loop.",
193 Self::DuplicateParam => "Duplicate function parameter name.",
194 Self::ReadUninit => "Use of possibly uninitialized variable.",
195 Self::UnresolvedIdent => "Unresolved reference.",
196 Self::IntParse => "Invalid integer literal.",
197 Self::FloatParse => "Invalid float literal.",
198 Self::UnreachableStatement => "Unreachable statement.",
199 Self::UnreachableArm => "Unreachable match arm.",
200 Self::DuplicateEntry => "Duplicate struct entry.",
201 Self::LiteralAssignment => "Assignment to literal is meaningless.",
202
203 Self::TypeMismatch => "Type mismatch.",
204 Self::NilIndex => "Index operator is not applicable to nil type.",
205 Self::IndexTypeMismatch => "Index type must be an integer or an integer range.",
206 Self::UndefinedOperator => "Unresolved operator.",
207 Self::UndefinedDisplay => "Type does not implement the Display operator.",
208 Self::CallArityMismatch => "Call arity mismatch.",
209 Self::FnArityMismatch => "Function arity mismatch.",
210 Self::ResultMismatch => "Function result type mismatch.",
211 Self::UnknownComponent => "Unknown field.",
212 Self::InconsistentReturns => "Missing trailing return statement.",
213 };
214
215 formatter.write_str(message)
216 }
217}
218
219impl IssueCode {
220 #[inline(always)]
223 pub fn severity(self) -> IssueSeverity {
224 match self {
225 Self::Parse => IssueSeverity::Error,
226
227 Self::UnresolvedPackage => IssueSeverity::Error,
228 Self::NotAPackage => IssueSeverity::Error,
229 Self::OrphanedBreak => IssueSeverity::Error,
230 Self::DuplicateParam => IssueSeverity::Error,
231 Self::ReadUninit => IssueSeverity::Error,
232 Self::UnresolvedIdent => IssueSeverity::Error,
233 Self::IntParse => IssueSeverity::Error,
234 Self::FloatParse => IssueSeverity::Error,
235 Self::UnreachableStatement => IssueSeverity::Warning,
236 Self::UnreachableArm => IssueSeverity::Warning,
237 Self::DuplicateEntry => IssueSeverity::Warning,
238 Self::LiteralAssignment => IssueSeverity::Warning,
239
240 Self::TypeMismatch => IssueSeverity::Warning,
241 Self::NilIndex => IssueSeverity::Warning,
242 Self::IndexTypeMismatch => IssueSeverity::Warning,
243 Self::UndefinedOperator => IssueSeverity::Warning,
244 Self::UndefinedDisplay => IssueSeverity::Warning,
245 Self::CallArityMismatch => IssueSeverity::Warning,
246 Self::FnArityMismatch => IssueSeverity::Warning,
247 Self::ResultMismatch => IssueSeverity::Warning,
248 Self::UnknownComponent => IssueSeverity::Warning,
249 Self::InconsistentReturns => IssueSeverity::Warning,
250 }
251 }
252
253 #[inline(always)]
257 pub fn depth(self) -> DiagnosticsDepth {
258 (self as u16 / 100) as DiagnosticsDepth
259 }
260}
261
262#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
264#[repr(u8)]
265pub enum IssueSeverity {
266 Error = 1 << 0,
269
270 Warning = 1 << 1,
274}
275
276impl Display for IssueSeverity {
277 #[inline(always)]
278 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
279 match self {
280 IssueSeverity::Error => formatter.write_str("error"),
281 IssueSeverity::Warning => formatter.write_str("warning"),
282 }
283 }
284}
285
286impl IssueSeverity {
287 #[inline(always)]
288 pub(crate) fn priority(self) -> AnnotationPriority {
289 match self {
290 Self::Error => AnnotationPriority::Primary,
291 Self::Warning => AnnotationPriority::Note,
292 }
293 }
294}
295
296#[derive(Clone, PartialEq, Eq, Hash)]
297pub(crate) enum ScriptIssue {
298 Parse {
299 error_ref: ErrorRef,
300 },
301
302 UnresolvedPackage {
303 base: &'static TypeMeta,
304 package_ref: NodeRef,
305 quickfix: CompactString,
306 },
307
308 NotAPackage {
309 ty: TypeHint,
310 package_ref: NodeRef,
311 },
312
313 OrphanedBreak {
314 break_ref: NodeRef,
315 },
316
317 DuplicateParam {
318 var_ref: NodeRef,
319 },
320
321 ReadUninit {
322 ident_ref: NodeRef,
323 },
324
325 UnresolvedIdent {
326 ident_ref: NodeRef,
327 quickfix: CompactString,
328 import: CompactString,
329 },
330
331 IntParse {
332 number_ref: NodeRef,
333 },
334
335 FloatParse {
336 number_ref: NodeRef,
337 },
338
339 UnreachableStatement {
340 st_ref: NodeRef,
341 },
342
343 UnreachableArm {
344 arm_ref: NodeRef,
345 },
346
347 DuplicateEntry {
348 entry_key_ref: NodeRef,
349 },
350
351 LiteralAssignment {
352 op_ref: NodeRef,
353 },
354
355 TypeMismatch {
356 expr_ref: NodeRef,
357 expected: &'static TypeFamily,
358 provided: &'static TypeFamily,
359 },
360
361 NilIndex {
362 op_ref: NodeRef,
363 },
364
365 IndexTypeMismatch {
366 arg_ref: NodeRef,
367 provided: &'static TypeFamily,
368 },
369
370 UndefinedOperator {
371 op_ref: NodeRef,
372 op: OperatorKind,
373 receiver: &'static TypeMeta,
374 },
375
376 CallArityMismatch {
377 args_ref: NodeRef,
378 expected: usize,
379 provided: usize,
380 },
381
382 FnArityMismatch {
383 arg_ref: NodeRef,
384 expected: usize,
385 provided: usize,
386 },
387
388 ResultMismatch {
389 arg_ref: NodeRef,
390 expected: &'static TypeFamily,
391 provided: &'static TypeFamily,
392 },
393
394 UnknownComponent {
395 field_ref: NodeRef,
396 receiver: &'static TypeMeta,
397 quickfix: CompactString,
398 },
399
400 InconsistentReturns {
401 fn_ref: NodeRef,
402 },
403}
404
405impl ScriptIssue {
406 pub(crate) fn code(&self) -> IssueCode {
407 match self {
408 Self::Parse { .. } => IssueCode::Parse,
409 Self::UnresolvedPackage { .. } => IssueCode::UnresolvedPackage,
410 Self::NotAPackage { .. } => IssueCode::NotAPackage,
411 Self::OrphanedBreak { .. } => IssueCode::OrphanedBreak,
412 Self::DuplicateParam { .. } => IssueCode::DuplicateParam,
413 Self::ReadUninit { .. } => IssueCode::ReadUninit,
414 Self::UnresolvedIdent { .. } => IssueCode::UnresolvedIdent,
415 Self::IntParse { .. } => IssueCode::IntParse,
416 Self::FloatParse { .. } => IssueCode::FloatParse,
417 Self::UnreachableStatement { .. } => IssueCode::UnreachableStatement,
418 Self::UnreachableArm { .. } => IssueCode::UnreachableArm,
419 Self::DuplicateEntry { .. } => IssueCode::DuplicateEntry,
420 Self::LiteralAssignment { .. } => IssueCode::LiteralAssignment,
421 Self::TypeMismatch { .. } => IssueCode::TypeMismatch,
422 Self::NilIndex { .. } => IssueCode::NilIndex,
423 Self::IndexTypeMismatch { .. } => IssueCode::IndexTypeMismatch,
424 Self::UndefinedOperator { .. } => IssueCode::UndefinedOperator,
425 Self::CallArityMismatch { .. } => IssueCode::CallArityMismatch,
426 Self::FnArityMismatch { .. } => IssueCode::FnArityMismatch,
427 Self::ResultMismatch { .. } => IssueCode::ResultMismatch,
428 Self::UnknownComponent { .. } => IssueCode::UnknownComponent,
429 Self::InconsistentReturns { .. } => IssueCode::InconsistentReturns,
430 }
431 }
432
433 pub(crate) fn span(&self, doc: &ScriptDoc) -> ScriptOrigin {
434 match self {
435 Self::Parse { error_ref, .. } => match error_ref.deref(doc) {
436 Some(issue) => ScriptOrigin::from(issue.aligned_span(doc)),
437 None => ScriptOrigin::invalid(error_ref.id),
438 },
439
440 Self::UnresolvedPackage { package_ref, .. } => {
441 package_ref.script_origin(doc, SpanBounds::Cover)
442 }
443
444 Self::NotAPackage { package_ref, .. } => {
445 package_ref.script_origin(doc, SpanBounds::Cover)
446 }
447
448 Self::OrphanedBreak { break_ref, .. } => {
449 break_ref.script_origin(doc, SpanBounds::Header)
450 }
451
452 Self::DuplicateParam { var_ref, .. } => var_ref.script_origin(doc, SpanBounds::Header),
453
454 Self::ReadUninit { ident_ref, .. } => ident_ref.script_origin(doc, SpanBounds::Cover),
455
456 Self::UnresolvedIdent { ident_ref, .. } => {
457 ident_ref.script_origin(doc, SpanBounds::Cover)
458 }
459
460 Self::IntParse { number_ref, .. } => number_ref.script_origin(doc, SpanBounds::Cover),
461
462 Self::FloatParse { number_ref, .. } => number_ref.script_origin(doc, SpanBounds::Cover),
463
464 Self::UnreachableStatement { st_ref, .. } => {
465 st_ref.script_origin(doc, SpanBounds::Cover)
466 }
467
468 Self::UnreachableArm { arm_ref, .. } => arm_ref.script_origin(doc, SpanBounds::Cover),
469
470 Self::DuplicateEntry { entry_key_ref, .. } => {
471 entry_key_ref.script_origin(doc, SpanBounds::Cover)
472 }
473
474 Self::LiteralAssignment { op_ref, .. } => op_ref.script_origin(doc, SpanBounds::Cover),
475
476 Self::TypeMismatch { expr_ref, .. } => expr_ref.script_origin(doc, SpanBounds::Cover),
477
478 Self::NilIndex { op_ref, .. } => op_ref.script_origin(doc, SpanBounds::Cover),
479
480 Self::IndexTypeMismatch { arg_ref, .. } => {
481 arg_ref.script_origin(doc, SpanBounds::Cover)
482 }
483
484 Self::UndefinedOperator { op_ref, .. } => op_ref.script_origin(doc, SpanBounds::Cover),
485
486 Self::CallArityMismatch { args_ref, .. } => {
487 args_ref.script_origin(doc, SpanBounds::Cover)
488 }
489
490 Self::FnArityMismatch { arg_ref, .. } => arg_ref.script_origin(doc, SpanBounds::Header),
491
492 Self::ResultMismatch { arg_ref, .. } => arg_ref.script_origin(doc, SpanBounds::Header),
493
494 Self::UnknownComponent { field_ref, .. } => {
495 field_ref.script_origin(doc, SpanBounds::Cover)
496 }
497
498 Self::InconsistentReturns { fn_ref, .. } => {
499 fn_ref.script_origin(doc, SpanBounds::Header)
500 }
501 }
502 }
503
504 pub(crate) fn message(&self, doc: &ScriptDoc) -> Cow<'static, str> {
505 match self {
506 Self::Parse { error_ref, .. } => Self::message_parse(doc, error_ref),
507
508 Self::UnresolvedPackage { base, quickfix, .. } => {
509 match (base.is_nil(), quickfix.is_empty()) {
510 (true, true) => Cow::from("unresolved import"),
511 (false, true) => Cow::from(format!("unresolved import from {base}")),
512 (true, false) => {
513 Cow::from(format!("unresolved import. did you mean {quickfix:?}?"))
514 }
515 (false, false) => Cow::from(format!(
516 "unresolved import from {base}. did you mean {quickfix:?}?"
517 )),
518 }
519 }
520
521 Self::NotAPackage { ty, .. } => Cow::from(format!("type '{ty}' is not a package")),
522
523 Self::OrphanedBreak { break_ref, .. } => match break_ref.deref(doc) {
524 Some(ScriptNode::Continue { .. }) => {
525 Cow::from("continue statement outside of a loop")
526 }
527 _ => Cow::from("break statement outside of a loop"),
528 },
529
530 Self::DuplicateParam { .. } => Cow::from("duplicate fn parameter name"),
531
532 Self::ReadUninit { .. } => Cow::from("use of possibly uninitialized variable"),
533
534 Self::UnresolvedIdent {
535 quickfix, import, ..
536 } => match (quickfix.is_empty(), import.is_empty()) {
537 (true, true) => Cow::from("unresolved reference"),
538
539 (false, true) => Cow::from(format!(
540 "unresolved reference. did you mean \"{quickfix}\"?"
541 )),
542
543 _ => Cow::from(format!(
544 "unresolved reference. did you mean \"{import}.{quickfix}\"?"
545 )),
546 },
547
548 Self::IntParse { .. } => Cow::from("invalid integer literal"),
549
550 Self::FloatParse { .. } => Cow::from("invalid float literal"),
551
552 Self::UnreachableStatement { .. } => Cow::from("unreachable statement"),
553
554 Self::UnreachableArm { .. } => Cow::from("unreachable match arm"),
555
556 Self::DuplicateEntry { .. } => Cow::from("duplicate struct entry"),
557
558 Self::LiteralAssignment { .. } => Cow::from("assignment to literal is meaningless"),
559
560 Self::TypeMismatch {
561 expected, provided, ..
562 } => {
563 let expected = TypeHint::from(*expected);
564 let provided = TypeHint::from(*provided);
565
566 Cow::from(format!(
567 "expected '{expected}' type, but '{provided}' found"
568 ))
569 }
570
571 Self::NilIndex { .. } => Cow::from("nil type cannot be indexed"),
572
573 Self::IndexTypeMismatch { provided, .. } => {
574 let numeric_family = <usize>::type_meta().family();
575 let range_family = <Range<usize>>::type_meta().family();
576
577 Cow::from(format!(
578 "expected '{numeric_family}' or '{range_family}' type, but '{provided}' found",
579 ))
580 }
581
582 Self::UndefinedOperator { receiver, op, .. } => {
583 let receiver = TypeHint::from(*receiver);
584
585 Cow::from(format!("'{receiver}' does not implement {op}"))
586 }
587
588 Self::CallArityMismatch {
589 expected, provided, ..
590 } => match *expected {
591 1 => Cow::from(format!("expected 1 argument, but {provided} provided",)),
592
593 _ => Cow::from(format!(
594 "expected {expected} arguments, but {provided} provided",
595 )),
596 },
597
598 Self::FnArityMismatch {
599 expected, provided, ..
600 } => Cow::from(format!(
601 "expected fn({expected}) function, but fn({provided}) provided",
602 )),
603
604 Self::ResultMismatch {
605 expected, provided, ..
606 } => {
607 let expected = TypeHint::from(*expected);
608 let provided = TypeHint::from(*provided);
609
610 Cow::from(format!(
611 "expected a function with '{expected}' return type, but the function returns '{provided}'"
612 ))
613 }
614
615 Self::UnknownComponent {
616 receiver, quickfix, ..
617 } => {
618 let receiver = TypeHint::from(*receiver);
619
620 match quickfix.is_empty() {
621 true => Cow::from(format!("unknown '{receiver}' field",)),
622
623 false => Cow::from(format!(
624 "unknown '{receiver}' field. did you mean {quickfix:?}?",
625 )),
626 }
627 }
628
629 Self::InconsistentReturns { .. } => Cow::from("missing trailing return expression"),
630 }
631 }
632
633 fn message_parse(doc: &ScriptDoc, error_ref: &ErrorRef) -> Cow<'static, str> {
634 let Some(issue) = error_ref.deref(doc) else {
635 return Cow::from("parse error");
636 };
637
638 match issue.context {
639 ScriptNode::STRING => Cow::from("unenclosed string literal"),
640 ScriptNode::MULTILINE_COMMENT => Cow::from("unenclosed comment"),
641 ScriptNode::BLOCK if issue.recovery == RecoveryResult::UnexpectedEOI => {
642 Cow::from("unenclosed code block")
643 }
644 ScriptNode::EXPR if issue.recovery == RecoveryResult::PanicRecover => {
645 Cow::from("unexpected operator")
646 }
647
648 _ => {
649 if Self::is_operator_rule(issue.context) {
650 return Cow::from("missing operand");
651 }
652
653 Cow::from(issue.message::<ScriptNode>(doc).to_string())
654 }
655 }
656 }
657
658 #[inline(always)]
659 fn is_operator_rule(rule: NodeRule) -> bool {
660 match rule {
661 ScriptNode::UNARY_LEFT | ScriptNode::BINARY | ScriptNode::QUERY => true,
662 _ => false,
663 }
664 }
665}