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 type `{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 type `{expected}`, but found type `{actual}`"
411 ))
412 .with_label(format!("this is type `{actual}`"), actual_span)
413 .with_label(format!("this expects type `{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 type `{expected}`, but found type `{actual}`",
431 ))
432 .with_label(
433 format!(
434 "input `{name}` is type `{expected}`, but name `{name}` is type `{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 type `{expected}` and type `{actual}` does not exist"
453 ))
454 .with_label(format!("this is type `{actual}`"), actual_span)
455 .with_label(
456 format!("this and all prior elements had a common type `{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 type `{actual}`",
470 expected = display_types(expected),
471 ))
472 .with_label(format!("this is type `{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 type `{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 type \
574 `{actual}`"
575 ))
576 .with_label(format!("this is type `{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 type \
608 `{actual}`"
609 ))
610 .with_label(format!("this is type `{actual}`"), actual_span)
611}
612
613pub fn negation_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
615 Diagnostic::error(format!(
616 "type mismatch: expected negation operand to be type `Int` or `Float`, but found type \
617 `{actual}`"
618 ))
619 .with_label(format!("this is type `{actual}`"), actual_span)
620}
621
622pub fn logical_or_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
624 Diagnostic::error(format!(
625 "type mismatch: expected `logical or` operand to be type `Boolean`, but found type \
626 `{actual}`"
627 ))
628 .with_label(format!("this is type `{actual}`"), actual_span)
629}
630
631pub fn logical_and_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
633 Diagnostic::error(format!(
634 "type mismatch: expected `logical and` operand to be type `Boolean`, but found type \
635 `{actual}`"
636 ))
637 .with_label(format!("this is type `{actual}`"), actual_span)
638}
639
640pub fn comparison_mismatch(
642 op: ComparisonOperator,
643 span: Span,
644 lhs: &Type,
645 lhs_span: Span,
646 rhs: &Type,
647 rhs_span: Span,
648) -> Diagnostic {
649 Diagnostic::error(format!(
650 "type mismatch: operator `{op}` cannot compare type `{lhs}` to type `{rhs}`"
651 ))
652 .with_highlight(span)
653 .with_label(format!("this is type `{lhs}`"), lhs_span)
654 .with_label(format!("this is type `{rhs}`"), rhs_span)
655}
656
657pub fn numeric_mismatch(
659 op: NumericOperator,
660 span: Span,
661 lhs: &Type,
662 lhs_span: Span,
663 rhs: &Type,
664 rhs_span: Span,
665) -> Diagnostic {
666 Diagnostic::error(format!(
667 "type mismatch: {op} operator is not supported for type `{lhs}` and type `{rhs}`"
668 ))
669 .with_highlight(span)
670 .with_label(format!("this is type `{lhs}`"), lhs_span)
671 .with_label(format!("this is type `{rhs}`"), rhs_span)
672}
673
674pub fn string_concat_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
676 Diagnostic::error(format!(
677 "type mismatch: string concatenation is not supported for type `{actual}`"
678 ))
679 .with_label(format!("this is type `{actual}`"), actual_span)
680}
681
682pub fn unknown_function(name: &str, span: Span) -> Diagnostic {
684 Diagnostic::error(format!("unknown function `{name}`")).with_label(
685 "the WDL standard library does not have a function with this name",
686 span,
687 )
688}
689
690pub fn unsupported_function(minimum: SupportedVersion, name: &str, span: Span) -> Diagnostic {
692 Diagnostic::error(format!(
693 "this use of function `{name}` requires a minimum WDL version of {minimum}"
694 ))
695 .with_highlight(span)
696}
697
698pub fn too_few_arguments(name: &str, span: Span, minimum: usize, count: usize) -> Diagnostic {
700 Diagnostic::error(format!(
701 "function `{name}` requires at least {minimum} argument{s} but {count} {v} supplied",
702 s = if minimum == 1 { "" } else { "s" },
703 v = if count == 1 { "was" } else { "were" },
704 ))
705 .with_highlight(span)
706}
707
708pub fn too_many_arguments(
710 name: &str,
711 span: Span,
712 maximum: usize,
713 count: usize,
714 excessive: impl Iterator<Item = Span>,
715) -> Diagnostic {
716 let mut diagnostic = Diagnostic::error(format!(
717 "function `{name}` requires no more than {maximum} argument{s} but {count} {v} supplied",
718 s = if maximum == 1 { "" } else { "s" },
719 v = if count == 1 { "was" } else { "were" },
720 ))
721 .with_highlight(span);
722
723 for span in excessive {
724 diagnostic = diagnostic.with_label("this argument is unexpected", span);
725 }
726
727 diagnostic
728}
729
730pub fn argument_type_mismatch(name: &str, expected: &str, actual: &Type, span: Span) -> Diagnostic {
732 Diagnostic::error(format!(
733 "type mismatch: argument to function `{name}` expects type {expected}, but found type \
734 `{actual}`"
735 ))
736 .with_label(format!("this is type `{actual}`"), span)
737}
738
739pub fn ambiguous_argument(name: &str, span: Span, first: &str, second: &str) -> Diagnostic {
741 Diagnostic::error(format!(
742 "ambiguous call to function `{name}` with conflicting signatures `{first}` and `{second}`",
743 ))
744 .with_highlight(span)
745}
746
747pub fn index_type_mismatch(expected: &Type, actual: &Type, span: Span) -> Diagnostic {
749 Diagnostic::error(format!(
750 "type mismatch: expected index to be type `{expected}`, but found type `{actual}`"
751 ))
752 .with_label(format!("this is type `{actual}`"), span)
753}
754
755pub fn type_is_not_array(actual: &Type, span: Span) -> Diagnostic {
757 Diagnostic::error(format!(
758 "type mismatch: expected an array type, but found type `{actual}`"
759 ))
760 .with_label(format!("this is type `{actual}`"), span)
761}
762
763pub fn cannot_access(actual: &Type, actual_span: Span) -> Diagnostic {
765 Diagnostic::error(format!("cannot access type `{actual}`"))
766 .with_label(format!("this is type `{actual}`"), actual_span)
767}
768
769pub fn cannot_coerce_to_string(actual: &Type, span: Span) -> Diagnostic {
771 Diagnostic::error(format!("cannot coerce type `{actual}` to `String`"))
772 .with_label(format!("this is type `{actual}`"), span)
773}
774
775pub fn unknown_task_or_workflow(namespace: Option<Span>, name: &str, span: Span) -> Diagnostic {
777 let mut diagnostic =
778 Diagnostic::error(format!("unknown task or workflow `{name}`")).with_highlight(span);
779
780 if let Some(namespace) = namespace {
781 diagnostic = diagnostic.with_label(
782 format!("this namespace does not have a task or workflow named `{name}`"),
783 namespace,
784 );
785 }
786
787 diagnostic
788}
789
790pub fn unknown_call_io<T: TreeToken>(call: &CallType, name: &Ident<T>, io: Io) -> Diagnostic {
792 Diagnostic::error(format!(
793 "{kind} `{call}` does not have an {io} named `{name}`",
794 kind = call.kind(),
795 call = call.name(),
796 name = name.text(),
797 ))
798 .with_highlight(name.span())
799}
800
801pub fn unknown_task_io<T: TreeToken>(task_name: &str, name: &Ident<T>, io: Io) -> Diagnostic {
803 Diagnostic::error(format!(
804 "task `{task_name}` does not have an {io} named `{name}`",
805 name = name.text(),
806 ))
807 .with_highlight(name.span())
808}
809
810pub fn recursive_workflow_call(name: &str, span: Span) -> Diagnostic {
812 Diagnostic::error(format!("cannot recursively call workflow `{name}`")).with_highlight(span)
813}
814
815pub fn missing_call_input<T: TreeToken>(
817 kind: CallKind,
818 target: &Ident<T>,
819 input: &str,
820 nested_inputs_allowed: bool,
821) -> Diagnostic {
822 let message = format!(
823 "missing required call input `{input}` for {kind} `{target}`",
824 target = target.text(),
825 );
826
827 if nested_inputs_allowed {
828 Diagnostic::warning(message).with_highlight(target.span())
829 } else {
830 Diagnostic::error(message).with_highlight(target.span())
831 }
832}
833
834pub fn unused_import(name: &str, span: Span) -> Diagnostic {
836 Diagnostic::warning(format!("unused import namespace `{name}`"))
837 .with_rule(UNUSED_IMPORT_RULE_ID)
838 .with_highlight(span)
839}
840
841pub fn unused_input(name: &str, span: Span) -> Diagnostic {
843 Diagnostic::warning(format!("unused input `{name}`"))
844 .with_rule(UNUSED_INPUT_RULE_ID)
845 .with_highlight(span)
846}
847
848pub fn unused_declaration(name: &str, span: Span) -> Diagnostic {
850 Diagnostic::warning(format!("unused declaration `{name}`"))
851 .with_rule(UNUSED_DECL_RULE_ID)
852 .with_highlight(span)
853}
854
855pub fn unused_call(name: &str, span: Span) -> Diagnostic {
857 Diagnostic::warning(format!("unused call `{name}`"))
858 .with_rule(UNUSED_CALL_RULE_ID)
859 .with_highlight(span)
860}
861
862pub fn unnecessary_function_call(
864 name: &str,
865 span: Span,
866 label: &str,
867 label_span: Span,
868) -> Diagnostic {
869 Diagnostic::warning(format!("unnecessary call to function `{name}`"))
870 .with_rule(UNNECESSARY_FUNCTION_CALL)
871 .with_highlight(span)
872 .with_label(label.to_string(), label_span)
873}
874
875pub fn invalid_placeholder_option<N: TreeNode>(
878 ty: &Type,
879 span: Span,
880 option: &PlaceholderOption<N>,
881) -> Diagnostic {
882 let message = match option {
883 PlaceholderOption::Sep(_) => format!(
884 "type mismatch for placeholder option `sep`: expected type `Array[P]` where P: any \
885 primitive type, but found `{ty}`"
886 ),
887 PlaceholderOption::Default(_) => format!(
888 "type mismatch for placeholder option `default`: expected any primitive type, but \
889 found `{ty}`"
890 ),
891 PlaceholderOption::TrueFalse(_) => format!(
892 "type mismatch for placeholder option `true/false`: expected type `Boolean`, but \
893 found `{ty}`"
894 ),
895 };
896
897 Diagnostic::error(message).with_label(format!("this is type `{ty}`"), span)
898}
899
900pub fn invalid_regex_pattern(
902 function: &str,
903 pattern: &str,
904 error: ®ex::Error,
905 span: Span,
906) -> Diagnostic {
907 Diagnostic::error(format!(
908 "invalid regular expression `{pattern}` used in function `{function}`: {error}"
909 ))
910 .with_label("invalid regular expression", span)
911}
912
913pub fn not_a_custom_type<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
915 Diagnostic::error(format!("`{}` is not a custom type", name.text())).with_label(
916 "only struct and enum types can be referenced as values",
917 name.span(),
918 )
919}
920
921pub fn no_common_inferred_type_for_enum(
926 enum_name: &str,
927 common_type: &Type,
928 common_span: Span,
929 discordant_type: &Type,
930 discordant_span: Span,
931) -> Diagnostic {
932 Diagnostic::error(format!("cannot infer a common type for enum `{enum_name}`"))
933 .with_label(
934 format!(
935 "this is the first variant with type `{discordant_type}` that has no common type \
936 with `{common_type}`"
937 ),
938 discordant_span,
939 )
940 .with_label(
941 format!("this is the last variant with a common type `{common_type}`"),
942 common_span,
943 )
944}
945
946pub fn enum_variant_does_not_coerce_to_type(
948 enum_name: &str,
949 enum_span: Span,
950 variant_name: &str,
951 variant_span: Span,
952 expected: &Type,
953 actual: &Type,
954) -> Diagnostic {
955 Diagnostic::error(format!(
956 "cannot coerce variant `{variant_name}` in enum `{enum_name}` from type `{actual}` to \
957 type `{expected}`"
958 ))
959 .with_label(format!("this is the `{enum_name}` enum"), enum_span)
960 .with_label(
961 format!("this is the `{variant_name}` variant"),
962 variant_span,
963 )
964 .with_fix(format!(
965 "change the value to something that coerces to type `{expected}` or explicitly set the \
966 enum's inner type"
967 ))
968}