1use std::fmt;
4
5use wdl_ast::AstToken;
6use wdl_ast::Diagnostic;
7use wdl_ast::Ident;
8use wdl_ast::Span;
9use wdl_ast::SupportedVersion;
10use wdl_ast::TreeNode;
11use wdl_ast::TreeToken;
12use wdl_ast::Version;
13use wdl_ast::v1::PlaceholderOption;
14
15use crate::UNNECESSARY_FUNCTION_CALL;
16use crate::UNUSED_CALL_RULE_ID;
17use crate::UNUSED_DECL_RULE_ID;
18use crate::UNUSED_IMPORT_RULE_ID;
19use crate::UNUSED_INPUT_RULE_ID;
20use crate::types::CallKind;
21use crate::types::CallType;
22use crate::types::Type;
23use crate::types::display_types;
24use crate::types::v1::ComparisonOperator;
25use crate::types::v1::NumericOperator;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Io {
30 Input,
32 Output,
34}
35
36impl fmt::Display for Io {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::Input => write!(f, "input"),
40 Self::Output => write!(f, "output"),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Context {
48 Namespace(Span),
50 Workflow(Span),
52 Task(Span),
54 Struct(Span),
56 StructMember(Span),
58 Enum(Span),
60 EnumVariant(Span),
62 Name(NameContext),
64}
65
66impl Context {
67 fn span(&self) -> Span {
69 match self {
70 Self::Namespace(s) => *s,
71 Self::Workflow(s) => *s,
72 Self::Task(s) => *s,
73 Self::Struct(s) => *s,
74 Self::StructMember(s) => *s,
75 Self::Enum(s) => *s,
76 Self::EnumVariant(s) => *s,
77 Self::Name(n) => n.span(),
78 }
79 }
80}
81
82impl fmt::Display for Context {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 Self::Namespace(_) => write!(f, "namespace"),
86 Self::Workflow(_) => write!(f, "workflow"),
87 Self::Task(_) => write!(f, "task"),
88 Self::Struct(_) => write!(f, "struct"),
89 Self::StructMember(_) => write!(f, "struct member"),
90 Self::Enum(_) => write!(f, "enum"),
91 Self::EnumVariant(_) => write!(f, "enum variant"),
92 Self::Name(n) => n.fmt(f),
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum NameContext {
100 Input(Span),
102 Output(Span),
104 Decl(Span),
106 Call(Span),
108 ScatterVariable(Span),
110}
111
112impl NameContext {
113 pub fn span(&self) -> Span {
115 match self {
116 Self::Input(s) => *s,
117 Self::Output(s) => *s,
118 Self::Decl(s) => *s,
119 Self::Call(s) => *s,
120 Self::ScatterVariable(s) => *s,
121 }
122 }
123}
124
125impl fmt::Display for NameContext {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 Self::Input(_) => write!(f, "input"),
129 Self::Output(_) => write!(f, "output"),
130 Self::Decl(_) => write!(f, "declaration"),
131 Self::Call(_) => write!(f, "call"),
132 Self::ScatterVariable(_) => write!(f, "scatter variable"),
133 }
134 }
135}
136
137impl From<NameContext> for Context {
138 fn from(context: NameContext) -> Self {
139 Self::Name(context)
140 }
141}
142
143pub fn name_conflict(name: &str, conflicting: Context, first: Context) -> Diagnostic {
145 Diagnostic::error(format!("conflicting {conflicting} name `{name}`"))
146 .with_label(
147 format!("this {conflicting} conflicts with a previously used name"),
148 conflicting.span(),
149 )
150 .with_label(
151 format!("the {first} with the conflicting name is here"),
152 first.span(),
153 )
154}
155
156pub fn cannot_index(actual: &Type, span: Span) -> Diagnostic {
158 Diagnostic::error("indexing is only allowed on `Array` and `Map` types")
159 .with_label(format!("this is {actual:#}"), span)
160}
161
162pub fn unknown_name(name: &str, span: Span) -> Diagnostic {
164 let message = match name {
166 "task" => "the `task` variable may only be used within a task command section or task \
167 output section using WDL 1.2 or later, or within a task requirements, task \
168 hints, or task runtime section using WDL 1.3 or later"
169 .to_string(),
170 _ => format!("unknown name `{name}`"),
171 };
172
173 Diagnostic::error(message).with_highlight(span)
174}
175
176pub fn self_referential(name: &str, span: Span, reference: Span) -> Diagnostic {
178 Diagnostic::error(format!("declaration of `{name}` is self-referential"))
179 .with_label("self-reference is here", reference)
180 .with_highlight(span)
181}
182
183pub fn task_reference_cycle(
185 from: &impl fmt::Display,
186 from_span: Span,
187 to: &str,
188 to_span: Span,
189) -> Diagnostic {
190 Diagnostic::error("a name reference cycle was detected")
191 .with_label(
192 format!("ensure this expression does not directly or indirectly refer to {from}"),
193 to_span,
194 )
195 .with_label(format!("a reference back to `{to}` is here"), from_span)
196}
197
198pub fn workflow_reference_cycle(
200 from: &impl fmt::Display,
201 from_span: Span,
202 to: &str,
203 to_span: Span,
204) -> Diagnostic {
205 Diagnostic::error("a name reference cycle was detected")
206 .with_label(format!("this name depends on {from}"), to_span)
207 .with_label(format!("a reference back to `{to}` is here"), from_span)
208}
209
210pub fn call_conflict<T: TreeToken>(
212 name: &Ident<T>,
213 first: NameContext,
214 suggest_fix: bool,
215) -> Diagnostic {
216 let diagnostic = Diagnostic::error(format!(
217 "conflicting call name `{name}`",
218 name = name.text()
219 ))
220 .with_label(
221 "this call name conflicts with a previously used name",
222 name.span(),
223 )
224 .with_label(
225 format!("the {first} with the conflicting name is here"),
226 first.span(),
227 );
228
229 if suggest_fix {
230 diagnostic.with_fix("add an `as` clause to the call to specify a different name")
231 } else {
232 diagnostic
233 }
234}
235
236pub fn namespace_conflict(
238 name: &str,
239 conflicting: Span,
240 first: Span,
241 suggest_fix: bool,
242) -> Diagnostic {
243 let diagnostic = Diagnostic::error(format!("conflicting import namespace `{name}`"))
244 .with_label("this conflicts with another import namespace", conflicting)
245 .with_label(
246 "the conflicting import namespace was introduced here",
247 first,
248 );
249
250 if suggest_fix {
251 diagnostic.with_fix("add an `as` clause to the import to specify a namespace")
252 } else {
253 diagnostic
254 }
255}
256
257pub fn unknown_namespace<T: TreeToken>(ns: &Ident<T>) -> Diagnostic {
259 Diagnostic::error(format!("unknown namespace `{ns}`", ns = ns.text())).with_highlight(ns.span())
260}
261
262pub fn only_one_namespace(span: Span) -> Diagnostic {
264 Diagnostic::error("only one namespace may be specified in a call statement")
265 .with_highlight(span)
266}
267
268pub fn import_cycle(span: Span) -> Diagnostic {
270 Diagnostic::error("import introduces a dependency cycle")
271 .with_label("this import has been skipped to break the cycle", span)
272}
273
274pub fn import_failure(uri: &str, error: &anyhow::Error, span: Span) -> Diagnostic {
276 Diagnostic::error(format!("failed to import `{uri}`: {error:#}")).with_highlight(span)
277}
278
279pub fn incompatible_import(
281 import_version: &str,
282 import_span: Span,
283 importer_version: &Version,
284) -> Diagnostic {
285 Diagnostic::error("imported document has incompatible version")
286 .with_label(
287 format!("the imported document is version `{import_version}`"),
288 import_span,
289 )
290 .with_label(
291 format!(
292 "the importing document is version `{version}`",
293 version = importer_version.text()
294 ),
295 importer_version.span(),
296 )
297}
298
299pub fn import_missing_version(span: Span) -> Diagnostic {
301 Diagnostic::error("imported document is missing a version statement").with_highlight(span)
302}
303
304pub fn invalid_relative_import(error: &url::ParseError, span: Span) -> Diagnostic {
306 Diagnostic::error(format!("{error:#}")).with_highlight(span)
307}
308
309pub fn struct_not_in_document<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
311 Diagnostic::error(format!(
312 "a struct named `{name}` does not exist in the imported document",
313 name = name.text()
314 ))
315 .with_label("this struct does not exist", name.span())
316}
317
318pub fn imported_struct_conflict(
320 name: &str,
321 conflicting: Span,
322 first: Span,
323 suggest_fix: bool,
324) -> Diagnostic {
325 let diagnostic = Diagnostic::error(format!("conflicting struct name `{name}`"))
326 .with_label(
327 "this import introduces a conflicting definition",
328 conflicting,
329 )
330 .with_label("the first definition was introduced by this import", first);
331
332 if suggest_fix {
333 diagnostic.with_fix("add an `alias` clause to the import to specify a different name")
334 } else {
335 diagnostic
336 }
337}
338
339pub fn struct_conflicts_with_import(name: &str, conflicting: Span, import: Span) -> Diagnostic {
341 Diagnostic::error(format!("conflicting struct name `{name}`"))
342 .with_label("this name conflicts with an imported struct", conflicting)
343 .with_label("the import that introduced the struct is here", import)
344 .with_fix(
345 "either rename the struct or use an `alias` clause on the import with a different name",
346 )
347}
348
349pub fn imported_enum_conflict(
351 name: &str,
352 conflicting: Span,
353 first: Span,
354 suggest_fix: bool,
355) -> Diagnostic {
356 let diagnostic = Diagnostic::error(format!("conflicting enum name `{name}`"))
357 .with_label(
358 "this import introduces a conflicting definition",
359 conflicting,
360 )
361 .with_label("the first definition was introduced by this import", first);
362
363 if suggest_fix {
364 diagnostic.with_fix("add an `alias` clause to the import to specify a different name")
365 } else {
366 diagnostic
367 }
368}
369
370pub fn enum_conflicts_with_import(name: &str, conflicting: Span, import: Span) -> Diagnostic {
372 Diagnostic::error(format!("conflicting enum name `{name}`"))
373 .with_label("this name conflicts with an imported enum", conflicting)
374 .with_label("the import that introduced the enum is here", import)
375 .with_fix(
376 "either rename the enum or use an `alias` clause on the import with a different name",
377 )
378}
379
380pub fn duplicate_workflow<T: TreeToken>(name: &Ident<T>, first: Span) -> Diagnostic {
382 Diagnostic::error(format!(
383 "cannot define workflow `{name}` as only one workflow is allowed per source file",
384 name = name.text(),
385 ))
386 .with_label("consider moving this workflow to a new file", name.span())
387 .with_label("first workflow is defined here", first)
388}
389
390pub fn recursive_struct(name: &str, span: Span, member: Span) -> Diagnostic {
392 Diagnostic::error(format!("struct `{name}` has a recursive definition"))
393 .with_highlight(span)
394 .with_label("this struct member participates in the recursion", member)
395}
396
397pub fn unknown_type(name: &str, span: Span) -> Diagnostic {
399 Diagnostic::error(format!("unknown type name `{name}`")).with_highlight(span)
400}
401
402pub fn type_mismatch(
404 expected: &Type,
405 expected_span: Span,
406 actual: &Type,
407 actual_span: Span,
408) -> Diagnostic {
409 Diagnostic::error(format!(
410 "type mismatch: expected {expected:#}, but found {actual:#}"
411 ))
412 .with_label(format!("this is {actual:#}"), actual_span)
413 .with_label(format!("this expects {expected:#}"), expected_span)
414}
415
416pub fn non_empty_array_assignment(expected_span: Span, actual_span: Span) -> Diagnostic {
418 Diagnostic::error("cannot assign an empty array to a non-empty array type")
419 .with_label("this is an empty array", actual_span)
420 .with_label("this expects a non-empty array", expected_span)
421}
422
423pub fn call_input_type_mismatch<T: TreeToken>(
425 name: &Ident<T>,
426 expected: &Type,
427 actual: &Type,
428) -> Diagnostic {
429 Diagnostic::error(format!(
430 "type mismatch: expected {expected:#}, but found {actual:#}",
431 ))
432 .with_label(
433 format!(
434 "input `{name}` is {expected:#}, but name `{name}` is {actual:#}",
435 name = name.text(),
436 ),
437 name.span(),
438 )
439}
440
441pub fn no_common_type(
446 expected: &Type,
447 expected_span: Span,
448 actual: &Type,
449 actual_span: Span,
450) -> Diagnostic {
451 Diagnostic::error(format!(
452 "type mismatch: a type common to both {expected:#} and {actual:#} does not exist"
453 ))
454 .with_label(format!("this is {actual:#}"), actual_span)
455 .with_label(
456 format!("this and all prior elements had a common {expected:#}"),
457 expected_span,
458 )
459}
460
461pub fn multiple_type_mismatch(
463 expected: &[Type],
464 expected_span: Span,
465 actual: &Type,
466 actual_span: Span,
467) -> Diagnostic {
468 Diagnostic::error(format!(
469 "type mismatch: expected {expected:#}, but found {actual:#}",
470 expected = display_types(expected),
471 ))
472 .with_label(format!("this is {actual:#}"), actual_span)
473 .with_label(
474 format!(
475 "this expects {expected:#}",
476 expected = display_types(expected)
477 ),
478 expected_span,
479 )
480}
481
482pub fn not_a_task_member<T: TreeToken>(member: &Ident<T>) -> Diagnostic {
484 Diagnostic::error(format!(
485 "the `task` variable does not have a member named `{member}`",
486 member = member.text()
487 ))
488 .with_highlight(member.span())
489}
490
491pub fn not_a_previous_task_data_member<T: TreeToken>(member: &Ident<T>) -> Diagnostic {
493 Diagnostic::error(format!(
494 "`task.previous` does not have a member named `{member}`",
495 member = member.text()
496 ))
497 .with_highlight(member.span())
498}
499
500pub fn not_a_struct<T: TreeToken>(member: &Ident<T>, input: bool) -> Diagnostic {
502 Diagnostic::error(format!(
503 "{kind} `{member}` is not a struct",
504 kind = if input { "input" } else { "struct member" },
505 member = member.text()
506 ))
507 .with_highlight(member.span())
508}
509
510pub fn not_a_struct_member<T: TreeToken>(name: &str, member: &Ident<T>) -> Diagnostic {
512 Diagnostic::error(format!(
513 "struct `{name}` does not have a member named `{member}`",
514 member = member.text()
515 ))
516 .with_highlight(member.span())
517}
518
519pub fn not_an_enum_variant<T: TreeToken>(name: &str, variant: &Ident<T>) -> Diagnostic {
521 Diagnostic::error(format!(
522 "enum `{name}` does not have a variant named `{variant}`",
523 variant = variant.text()
524 ))
525 .with_highlight(variant.span())
526}
527
528pub fn non_literal_enum_value(span: Span) -> Diagnostic {
530 Diagnostic::error("enum variant value must be a literal expression")
531 .with_highlight(span)
532 .with_fix(
533 "enum values must be literal expressions only (string literals, numeric literals, \
534 collection literals, or struct literals); string interpolation, variable references, \
535 and computed expressions are not allowed",
536 )
537}
538
539pub fn not_a_pair_accessor<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
541 Diagnostic::error(format!(
542 "cannot access a pair with name `{name}`",
543 name = name.text()
544 ))
545 .with_highlight(name.span())
546 .with_fix("use `left` or `right` to access a pair")
547}
548
549pub fn missing_struct_members<T: TreeToken>(
551 name: &Ident<T>,
552 count: usize,
553 members: &str,
554) -> Diagnostic {
555 Diagnostic::error(format!(
556 "struct `{name}` requires a value for member{s} {members}",
557 name = name.text(),
558 s = if count > 1 { "s" } else { "" },
559 ))
560 .with_highlight(name.span())
561}
562
563pub fn map_key_not_primitive(span: Span, actual: &Type) -> Diagnostic {
565 Diagnostic::error("expected map key to be a non-optional primitive type")
566 .with_highlight(span)
567 .with_label(format!("this is {actual:#}"), span)
568}
569
570pub fn if_conditional_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
572 Diagnostic::error(format!(
573 "type mismatch: expected `if` conditional expression to be type `Boolean`, but found \
574 {actual:#}"
575 ))
576 .with_label(format!("this is {actual:#}"), actual_span)
577}
578
579pub fn else_if_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
581 Diagnostic::error(format!(
582 "`else if` conditional clauses are not supported in WDL v{version}"
583 ))
584 .with_label("this `else if` is not supported", span)
585 .with_fix("use WDL v1.3 or higher to use `else if` conditional clauses")
586}
587
588pub fn else_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
590 Diagnostic::error(format!(
591 "`else` conditional clauses are not supported in WDL v{version}"
592 ))
593 .with_label("this `else` is not supported", span)
594 .with_fix("use WDL v1.3 or higher to use `else` conditional clauses")
595}
596
597pub fn enum_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
599 Diagnostic::error(format!("enums are not supported in WDL v{version}"))
600 .with_label("this enum is not supported", span)
601 .with_fix("use WDL v1.3 or higher to use enums")
602}
603
604pub fn logical_not_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
606 Diagnostic::error(format!(
607 "type mismatch: expected `logical not` operand to be type `Boolean`, but found {actual:#}"
608 ))
609 .with_label(format!("this is {actual:#}"), actual_span)
610}
611
612pub fn negation_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
614 Diagnostic::error(format!(
615 "type mismatch: expected negation operand to be type `Int` or `Float`, but found \
616 {actual:#}"
617 ))
618 .with_label(format!("this is {actual:#}"), actual_span)
619}
620
621pub fn logical_or_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
623 Diagnostic::error(format!(
624 "type mismatch: expected `logical or` operand to be type `Boolean`, but found {actual:#}"
625 ))
626 .with_label(format!("this is {actual:#}"), actual_span)
627}
628
629pub fn logical_and_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
631 Diagnostic::error(format!(
632 "type mismatch: expected `logical and` operand to be type `Boolean`, but found {actual:#}"
633 ))
634 .with_label(format!("this is {actual:#}"), actual_span)
635}
636
637pub fn comparison_mismatch(
639 op: ComparisonOperator,
640 span: Span,
641 lhs: &Type,
642 lhs_span: Span,
643 rhs: &Type,
644 rhs_span: Span,
645) -> Diagnostic {
646 Diagnostic::error(format!(
647 "type mismatch: operator `{op}` cannot compare {lhs:#} to {rhs:#}"
648 ))
649 .with_highlight(span)
650 .with_label(format!("this is {lhs:#}"), lhs_span)
651 .with_label(format!("this is {rhs:#}"), rhs_span)
652}
653
654pub fn numeric_mismatch(
656 op: NumericOperator,
657 span: Span,
658 lhs: &Type,
659 lhs_span: Span,
660 rhs: &Type,
661 rhs_span: Span,
662) -> Diagnostic {
663 Diagnostic::error(format!(
664 "type mismatch: {op} operator is not supported for {lhs:#} and {rhs:#}"
665 ))
666 .with_highlight(span)
667 .with_label(format!("this is {lhs:#}"), lhs_span)
668 .with_label(format!("this is {rhs:#}"), rhs_span)
669}
670
671pub fn string_concat_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
673 Diagnostic::error(format!(
674 "type mismatch: string concatenation is not supported for {actual:#}"
675 ))
676 .with_label(format!("this is {actual:#}"), actual_span)
677}
678
679pub fn unknown_function(name: &str, span: Span) -> Diagnostic {
681 Diagnostic::error(format!("unknown function `{name}`")).with_label(
682 "the WDL standard library does not have a function with this name",
683 span,
684 )
685}
686
687pub fn unsupported_function(minimum: SupportedVersion, name: &str, span: Span) -> Diagnostic {
689 Diagnostic::error(format!(
690 "this use of function `{name}` requires a minimum WDL version of {minimum}"
691 ))
692 .with_highlight(span)
693}
694
695pub fn too_few_arguments(name: &str, span: Span, minimum: usize, count: usize) -> Diagnostic {
697 Diagnostic::error(format!(
698 "function `{name}` requires at least {minimum} argument{s} but {count} {v} supplied",
699 s = if minimum == 1 { "" } else { "s" },
700 v = if count == 1 { "was" } else { "were" },
701 ))
702 .with_highlight(span)
703}
704
705pub fn too_many_arguments(
707 name: &str,
708 span: Span,
709 maximum: usize,
710 count: usize,
711 excessive: impl Iterator<Item = Span>,
712) -> Diagnostic {
713 let mut diagnostic = Diagnostic::error(format!(
714 "function `{name}` requires no more than {maximum} argument{s} but {count} {v} supplied",
715 s = if maximum == 1 { "" } else { "s" },
716 v = if count == 1 { "was" } else { "were" },
717 ))
718 .with_highlight(span);
719
720 for span in excessive {
721 diagnostic = diagnostic.with_label("this argument is unexpected", span);
722 }
723
724 diagnostic
725}
726
727pub fn argument_type_mismatch(name: &str, expected: &str, actual: &Type, span: Span) -> Diagnostic {
729 Diagnostic::error(format!(
730 "type mismatch: argument to function `{name}` expects {expected}, but found {actual:#}"
731 ))
732 .with_label(format!("this is {actual:#}"), span)
733}
734
735pub fn ambiguous_argument(name: &str, span: Span, first: &str, second: &str) -> Diagnostic {
737 Diagnostic::error(format!(
738 "ambiguous call to function `{name}` with conflicting signatures `{first}` and `{second}`",
739 ))
740 .with_highlight(span)
741}
742
743pub fn index_type_mismatch(expected: &Type, actual: &Type, span: Span) -> Diagnostic {
745 Diagnostic::error(format!(
746 "type mismatch: expected index to be {expected:#}, but found {actual:#}"
747 ))
748 .with_label(format!("this is {actual:#}"), span)
749}
750
751pub fn type_is_not_array(actual: &Type, span: Span) -> Diagnostic {
753 Diagnostic::error(format!(
754 "type mismatch: expected an array type, but found {actual:#}"
755 ))
756 .with_label(format!("this is {actual:#}"), span)
757}
758
759pub fn cannot_access(actual: &Type, actual_span: Span) -> Diagnostic {
761 Diagnostic::error(format!("cannot access {actual:#}"))
762 .with_label(format!("this is {actual:#}"), actual_span)
763}
764
765pub fn cannot_coerce_to_string(actual: &Type, span: Span) -> Diagnostic {
767 Diagnostic::error(format!("cannot coerce {actual:#} to type `String`"))
768 .with_label(format!("this is {actual:#}"), span)
769}
770
771pub fn unknown_task_or_workflow(namespace: Option<Span>, name: &str, span: Span) -> Diagnostic {
773 let mut diagnostic =
774 Diagnostic::error(format!("unknown task or workflow `{name}`")).with_highlight(span);
775
776 if let Some(namespace) = namespace {
777 diagnostic = diagnostic.with_label(
778 format!("this namespace does not have a task or workflow named `{name}`"),
779 namespace,
780 );
781 }
782
783 diagnostic
784}
785
786pub fn unknown_call_io<T: TreeToken>(call: &CallType, name: &Ident<T>, io: Io) -> Diagnostic {
788 Diagnostic::error(format!(
789 "{kind} `{call}` does not have an {io} named `{name}`",
790 kind = call.kind(),
791 call = call.name(),
792 name = name.text(),
793 ))
794 .with_highlight(name.span())
795}
796
797pub fn unknown_task_io<T: TreeToken>(task_name: &str, name: &Ident<T>, io: Io) -> Diagnostic {
799 Diagnostic::error(format!(
800 "task `{task_name}` does not have an {io} named `{name}`",
801 name = name.text(),
802 ))
803 .with_highlight(name.span())
804}
805
806pub fn recursive_workflow_call(name: &str, span: Span) -> Diagnostic {
808 Diagnostic::error(format!("cannot recursively call workflow `{name}`")).with_highlight(span)
809}
810
811pub fn missing_call_input<T: TreeToken>(
813 kind: CallKind,
814 target: &Ident<T>,
815 input: &str,
816 nested_inputs_allowed: bool,
817) -> Diagnostic {
818 let message = format!(
819 "missing required call input `{input}` for {kind} `{target}`",
820 target = target.text(),
821 );
822
823 if nested_inputs_allowed {
824 Diagnostic::warning(message).with_highlight(target.span())
825 } else {
826 Diagnostic::error(message).with_highlight(target.span())
827 }
828}
829
830pub fn unused_import(name: &str, span: Span) -> Diagnostic {
832 Diagnostic::warning(format!("unused import namespace `{name}`"))
833 .with_rule(UNUSED_IMPORT_RULE_ID)
834 .with_highlight(span)
835}
836
837pub fn unused_input(name: &str, span: Span) -> Diagnostic {
839 Diagnostic::warning(format!("unused input `{name}`"))
840 .with_rule(UNUSED_INPUT_RULE_ID)
841 .with_highlight(span)
842}
843
844pub fn unused_declaration(name: &str, span: Span) -> Diagnostic {
846 Diagnostic::warning(format!("unused declaration `{name}`"))
847 .with_rule(UNUSED_DECL_RULE_ID)
848 .with_highlight(span)
849}
850
851pub fn unused_call(name: &str, span: Span) -> Diagnostic {
853 Diagnostic::warning(format!("unused call `{name}`"))
854 .with_rule(UNUSED_CALL_RULE_ID)
855 .with_highlight(span)
856}
857
858pub fn unnecessary_function_call(
860 name: &str,
861 span: Span,
862 label: &str,
863 label_span: Span,
864) -> Diagnostic {
865 Diagnostic::warning(format!("unnecessary call to function `{name}`"))
866 .with_rule(UNNECESSARY_FUNCTION_CALL)
867 .with_highlight(span)
868 .with_label(label.to_string(), label_span)
869}
870
871pub fn invalid_placeholder_option<N: TreeNode>(
874 ty: &Type,
875 span: Span,
876 option: &PlaceholderOption<N>,
877) -> Diagnostic {
878 let message = match option {
879 PlaceholderOption::Sep(_) => format!(
880 "type mismatch for placeholder option `sep`: expected type `Array[P]` where P: any \
881 primitive type, but found {ty:#}"
882 ),
883 PlaceholderOption::Default(_) => format!(
884 "type mismatch for placeholder option `default`: expected any primitive type, but \
885 found {ty:#}"
886 ),
887 PlaceholderOption::TrueFalse(_) => format!(
888 "type mismatch for placeholder option `true/false`: expected type `Boolean`, but \
889 found {ty:#}"
890 ),
891 };
892
893 Diagnostic::error(message).with_label(format!("this is {ty:#}"), span)
894}
895
896pub fn invalid_regex_pattern(
898 function: &str,
899 pattern: &str,
900 error: ®ex::Error,
901 span: Span,
902) -> Diagnostic {
903 Diagnostic::error(format!(
904 "invalid regular expression `{pattern}` used in function `{function}`: {error}"
905 ))
906 .with_label("invalid regular expression", span)
907}
908
909pub fn not_a_custom_type<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
911 Diagnostic::error(format!("`{}` is not a custom type", name.text())).with_label(
912 "only struct and enum types can be referenced as values",
913 name.span(),
914 )
915}
916
917pub fn no_common_inferred_type_for_enum(
922 enum_name: &str,
923 common_type: &Type,
924 common_span: Span,
925 discordant_type: &Type,
926 discordant_span: Span,
927) -> Diagnostic {
928 Diagnostic::error(format!("cannot infer a common type for enum `{enum_name}`"))
929 .with_label(
930 format!(
931 "this is the first variant with {discordant_type:#} that has no common type with \
932 {common_type:#}"
933 ),
934 discordant_span,
935 )
936 .with_label(
937 format!("this is the last variant with a common {common_type:#}"),
938 common_span,
939 )
940}
941
942pub fn enum_variant_does_not_coerce_to_type(
944 enum_name: &str,
945 enum_span: Span,
946 variant_name: &str,
947 variant_span: Span,
948 expected: &Type,
949 actual: &Type,
950) -> Diagnostic {
951 Diagnostic::error(format!(
952 "cannot coerce variant `{variant_name}` in enum `{enum_name}` from {actual:#} to \
953 {expected:#}"
954 ))
955 .with_label(format!("this is the `{enum_name}` enum"), enum_span)
956 .with_label(
957 format!("this is the `{variant_name}` variant"),
958 variant_span,
959 )
960 .with_fix(format!(
961 "change the value to something that coerces to {expected:#} or explicitly set the enum's \
962 inner type"
963 ))
964}