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 Workflow(Span),
50 Task(Span),
52 Struct(Span),
54 StructMember(Span),
56 Name(NameContext),
58}
59
60impl Context {
61 fn span(&self) -> Span {
63 match self {
64 Self::Workflow(s) => *s,
65 Self::Task(s) => *s,
66 Self::Struct(s) => *s,
67 Self::StructMember(s) => *s,
68 Self::Name(n) => n.span(),
69 }
70 }
71}
72
73impl fmt::Display for Context {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::Workflow(_) => write!(f, "workflow"),
77 Self::Task(_) => write!(f, "task"),
78 Self::Struct(_) => write!(f, "struct"),
79 Self::StructMember(_) => write!(f, "struct member"),
80 Self::Name(n) => n.fmt(f),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum NameContext {
88 Input(Span),
90 Output(Span),
92 Decl(Span),
94 Call(Span),
96 ScatterVariable(Span),
98}
99
100impl NameContext {
101 pub fn span(&self) -> Span {
103 match self {
104 Self::Input(s) => *s,
105 Self::Output(s) => *s,
106 Self::Decl(s) => *s,
107 Self::Call(s) => *s,
108 Self::ScatterVariable(s) => *s,
109 }
110 }
111}
112
113impl fmt::Display for NameContext {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::Input(_) => write!(f, "input"),
117 Self::Output(_) => write!(f, "output"),
118 Self::Decl(_) => write!(f, "declaration"),
119 Self::Call(_) => write!(f, "call"),
120 Self::ScatterVariable(_) => write!(f, "scatter variable"),
121 }
122 }
123}
124
125impl From<NameContext> for Context {
126 fn from(context: NameContext) -> Self {
127 Self::Name(context)
128 }
129}
130
131pub fn name_conflict(name: &str, conflicting: Context, first: Context) -> Diagnostic {
133 Diagnostic::error(format!("conflicting {conflicting} name `{name}`"))
134 .with_label(
135 format!("this {conflicting} conflicts with a previously used name"),
136 conflicting.span(),
137 )
138 .with_label(
139 format!("the {first} with the conflicting name is here"),
140 first.span(),
141 )
142}
143
144pub fn cannot_index(actual: &Type, span: Span) -> Diagnostic {
146 Diagnostic::error("indexing is only allowed on `Array` and `Map` types")
147 .with_label(format!("this is type `{actual}`"), span)
148}
149
150pub fn unknown_name(name: &str, span: Span) -> Diagnostic {
152 let message = match name {
154 "task" => "the `task` variable may only be used within a task command section or task \
155 output section using WDL 1.2 or later"
156 .to_string(),
157 _ => format!("unknown name `{name}`"),
158 };
159
160 Diagnostic::error(message).with_highlight(span)
161}
162
163pub fn self_referential(name: &str, span: Span, reference: Span) -> Diagnostic {
165 Diagnostic::error(format!("declaration of `{name}` is self-referential"))
166 .with_label("self-reference is here", reference)
167 .with_highlight(span)
168}
169
170pub fn task_reference_cycle(
172 from: &impl fmt::Display,
173 from_span: Span,
174 to: &str,
175 to_span: Span,
176) -> Diagnostic {
177 Diagnostic::error("a name reference cycle was detected")
178 .with_label(
179 format!("ensure this expression does not directly or indirectly refer to {from}"),
180 to_span,
181 )
182 .with_label(format!("a reference back to `{to}` is here"), from_span)
183}
184
185pub fn workflow_reference_cycle(
187 from: &impl fmt::Display,
188 from_span: Span,
189 to: &str,
190 to_span: Span,
191) -> Diagnostic {
192 Diagnostic::error("a name reference cycle was detected")
193 .with_label(format!("this name depends on {from}"), to_span)
194 .with_label(format!("a reference back to `{to}` is here"), from_span)
195}
196
197pub fn call_conflict<T: TreeToken>(
199 name: &Ident<T>,
200 first: NameContext,
201 suggest_fix: bool,
202) -> Diagnostic {
203 let diagnostic = Diagnostic::error(format!(
204 "conflicting call name `{name}`",
205 name = name.text()
206 ))
207 .with_label(
208 "this call name conflicts with a previously used name",
209 name.span(),
210 )
211 .with_label(
212 format!("the {first} with the conflicting name is here"),
213 first.span(),
214 );
215
216 if suggest_fix {
217 diagnostic.with_fix("add an `as` clause to the call to specify a different name")
218 } else {
219 diagnostic
220 }
221}
222
223pub fn namespace_conflict(
225 name: &str,
226 conflicting: Span,
227 first: Span,
228 suggest_fix: bool,
229) -> Diagnostic {
230 let diagnostic = Diagnostic::error(format!("conflicting import namespace `{name}`"))
231 .with_label("this conflicts with another import namespace", conflicting)
232 .with_label(
233 "the conflicting import namespace was introduced here",
234 first,
235 );
236
237 if suggest_fix {
238 diagnostic.with_fix("add an `as` clause to the import to specify a namespace")
239 } else {
240 diagnostic
241 }
242}
243
244pub fn unknown_namespace<T: TreeToken>(ns: &Ident<T>) -> Diagnostic {
246 Diagnostic::error(format!("unknown namespace `{ns}`", ns = ns.text())).with_highlight(ns.span())
247}
248
249pub fn only_one_namespace(span: Span) -> Diagnostic {
251 Diagnostic::error("only one namespace may be specified in a call statement")
252 .with_highlight(span)
253}
254
255pub fn import_cycle(span: Span) -> Diagnostic {
257 Diagnostic::error("import introduces a dependency cycle")
258 .with_label("this import has been skipped to break the cycle", span)
259}
260
261pub fn import_failure(uri: &str, error: &anyhow::Error, span: Span) -> Diagnostic {
263 Diagnostic::error(format!("failed to import `{uri}`: {error:?}")).with_highlight(span)
264}
265
266pub fn incompatible_import(
268 import_version: &str,
269 import_span: Span,
270 importer_version: &Version,
271) -> Diagnostic {
272 Diagnostic::error("imported document has incompatible version")
273 .with_label(
274 format!("the imported document is version `{import_version}`"),
275 import_span,
276 )
277 .with_label(
278 format!(
279 "the importing document is version `{version}`",
280 version = importer_version.text()
281 ),
282 importer_version.span(),
283 )
284}
285
286pub fn import_missing_version(span: Span) -> Diagnostic {
288 Diagnostic::error("imported document is missing a version statement").with_highlight(span)
289}
290
291pub fn invalid_relative_import(error: &url::ParseError, span: Span) -> Diagnostic {
293 Diagnostic::error(format!("{error:?}")).with_highlight(span)
294}
295
296pub fn struct_not_in_document<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
298 Diagnostic::error(format!(
299 "a struct named `{name}` does not exist in the imported document",
300 name = name.text()
301 ))
302 .with_label("this struct does not exist", name.span())
303}
304
305pub fn imported_struct_conflict(
307 name: &str,
308 conflicting: Span,
309 first: Span,
310 suggest_fix: bool,
311) -> Diagnostic {
312 let diagnostic = Diagnostic::error(format!("conflicting struct name `{name}`"))
313 .with_label(
314 "this import introduces a conflicting definition",
315 conflicting,
316 )
317 .with_label("the first definition was introduced by this import", first);
318
319 if suggest_fix {
320 diagnostic.with_fix("add an `alias` clause to the import to specify a different name")
321 } else {
322 diagnostic
323 }
324}
325
326pub fn struct_conflicts_with_import(name: &str, conflicting: Span, import: Span) -> Diagnostic {
328 Diagnostic::error(format!("conflicting struct name `{name}`"))
329 .with_label("this name conflicts with an imported struct", conflicting)
330 .with_label("the import that introduced the struct is here", import)
331 .with_fix(
332 "either rename the struct or use an `alias` clause on the import with a different name",
333 )
334}
335
336pub fn duplicate_workflow<T: TreeToken>(name: &Ident<T>, first: Span) -> Diagnostic {
338 Diagnostic::error(format!(
339 "cannot define workflow `{name}` as only one workflow is allowed per source file",
340 name = name.text(),
341 ))
342 .with_label("consider moving this workflow to a new file", name.span())
343 .with_label("first workflow is defined here", first)
344}
345
346pub fn recursive_struct(name: &str, span: Span, member: Span) -> Diagnostic {
348 Diagnostic::error(format!("struct `{name}` has a recursive definition"))
349 .with_highlight(span)
350 .with_label("this struct member participates in the recursion", member)
351}
352
353pub fn unknown_type(name: &str, span: Span) -> Diagnostic {
355 Diagnostic::error(format!("unknown type name `{name}`")).with_highlight(span)
356}
357
358pub fn type_mismatch(
360 expected: &Type,
361 expected_span: Span,
362 actual: &Type,
363 actual_span: Span,
364) -> Diagnostic {
365 Diagnostic::error(format!(
366 "type mismatch: expected type `{expected}`, but found type `{actual}`"
367 ))
368 .with_label(format!("this is type `{actual}`"), actual_span)
369 .with_label(format!("this expects type `{expected}`"), expected_span)
370}
371
372pub fn non_empty_array_assignment(expected_span: Span, actual_span: Span) -> Diagnostic {
374 Diagnostic::error("cannot assign an empty array to a non-empty array type")
375 .with_label("this is an empty array", actual_span)
376 .with_label("this expects a non-empty array", expected_span)
377}
378
379pub fn call_input_type_mismatch<T: TreeToken>(
381 name: &Ident<T>,
382 expected: &Type,
383 actual: &Type,
384) -> Diagnostic {
385 Diagnostic::error(format!(
386 "type mismatch: expected type `{expected}`, but found type `{actual}`",
387 ))
388 .with_label(
389 format!(
390 "input `{name}` is type `{expected}`, but name `{name}` is type `{actual}`",
391 name = name.text(),
392 ),
393 name.span(),
394 )
395}
396
397pub fn no_common_type(
402 expected: &Type,
403 expected_span: Span,
404 actual: &Type,
405 actual_span: Span,
406) -> Diagnostic {
407 Diagnostic::error(format!(
408 "type mismatch: a type common to both type `{expected}` and type `{actual}` does not exist"
409 ))
410 .with_label(format!("this is type `{actual}`"), actual_span)
411 .with_label(
412 format!("this and all prior elements had a common type `{expected}`"),
413 expected_span,
414 )
415}
416
417pub fn multiple_type_mismatch(
419 expected: &[Type],
420 expected_span: Span,
421 actual: &Type,
422 actual_span: Span,
423) -> Diagnostic {
424 Diagnostic::error(format!(
425 "type mismatch: expected {expected}, but found type `{actual}`",
426 expected = display_types(expected),
427 ))
428 .with_label(format!("this is type `{actual}`"), actual_span)
429 .with_label(
430 format!(
431 "this expects {expected}",
432 expected = display_types(expected)
433 ),
434 expected_span,
435 )
436}
437
438pub fn not_a_task_member<T: TreeToken>(member: &Ident<T>) -> Diagnostic {
440 Diagnostic::error(format!(
441 "the `task` variable does not have a member named `{member}`",
442 member = member.text()
443 ))
444 .with_highlight(member.span())
445}
446
447pub fn not_a_struct<T: TreeToken>(member: &Ident<T>, input: bool) -> Diagnostic {
449 Diagnostic::error(format!(
450 "{kind} `{member}` is not a struct",
451 kind = if input { "input" } else { "struct member" },
452 member = member.text()
453 ))
454 .with_highlight(member.span())
455}
456
457pub fn not_a_struct_member<T: TreeToken>(name: &str, member: &Ident<T>) -> Diagnostic {
459 Diagnostic::error(format!(
460 "struct `{name}` does not have a member named `{member}`",
461 member = member.text()
462 ))
463 .with_highlight(member.span())
464}
465
466pub fn not_a_pair_accessor<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
468 Diagnostic::error(format!(
469 "cannot access a pair with name `{name}`",
470 name = name.text()
471 ))
472 .with_highlight(name.span())
473 .with_fix("use `left` or `right` to access a pair")
474}
475
476pub fn missing_struct_members<T: TreeToken>(
478 name: &Ident<T>,
479 count: usize,
480 members: &str,
481) -> Diagnostic {
482 Diagnostic::error(format!(
483 "struct `{name}` requires a value for member{s} {members}",
484 name = name.text(),
485 s = if count > 1 { "s" } else { "" },
486 ))
487 .with_highlight(name.span())
488}
489
490pub fn map_key_not_primitive(span: Span, actual: &Type) -> Diagnostic {
492 Diagnostic::error("expected map literal to use primitive type keys")
493 .with_highlight(span)
494 .with_label(format!("this is type `{actual}`"), span)
495}
496
497pub fn if_conditional_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
499 Diagnostic::error(format!(
500 "type mismatch: expected `if` conditional expression to be type `Boolean`, but found type \
501 `{actual}`"
502 ))
503 .with_label(format!("this is type `{actual}`"), actual_span)
504}
505
506pub fn logical_not_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
508 Diagnostic::error(format!(
509 "type mismatch: expected `logical not` operand to be type `Boolean`, but found type \
510 `{actual}`"
511 ))
512 .with_label(format!("this is type `{actual}`"), actual_span)
513}
514
515pub fn negation_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
517 Diagnostic::error(format!(
518 "type mismatch: expected negation operand to be type `Int` or `Float`, but found type \
519 `{actual}`"
520 ))
521 .with_label(format!("this is type `{actual}`"), actual_span)
522}
523
524pub fn logical_or_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
526 Diagnostic::error(format!(
527 "type mismatch: expected `logical or` operand to be type `Boolean`, but found type \
528 `{actual}`"
529 ))
530 .with_label(format!("this is type `{actual}`"), actual_span)
531}
532
533pub fn logical_and_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
535 Diagnostic::error(format!(
536 "type mismatch: expected `logical and` operand to be type `Boolean`, but found type \
537 `{actual}`"
538 ))
539 .with_label(format!("this is type `{actual}`"), actual_span)
540}
541
542pub fn comparison_mismatch(
544 op: ComparisonOperator,
545 span: Span,
546 lhs: &Type,
547 lhs_span: Span,
548 rhs: &Type,
549 rhs_span: Span,
550) -> Diagnostic {
551 Diagnostic::error(format!(
552 "type mismatch: operator `{op}` cannot compare type `{lhs}` to type `{rhs}`"
553 ))
554 .with_highlight(span)
555 .with_label(format!("this is type `{lhs}`"), lhs_span)
556 .with_label(format!("this is type `{rhs}`"), rhs_span)
557}
558
559pub fn numeric_mismatch(
561 op: NumericOperator,
562 span: Span,
563 lhs: &Type,
564 lhs_span: Span,
565 rhs: &Type,
566 rhs_span: Span,
567) -> Diagnostic {
568 Diagnostic::error(format!(
569 "type mismatch: {op} operator is not supported for type `{lhs}` and type `{rhs}`"
570 ))
571 .with_highlight(span)
572 .with_label(format!("this is type `{lhs}`"), lhs_span)
573 .with_label(format!("this is type `{rhs}`"), rhs_span)
574}
575
576pub fn string_concat_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
578 Diagnostic::error(format!(
579 "type mismatch: string concatenation is not supported for type `{actual}`"
580 ))
581 .with_label(format!("this is type `{actual}`"), actual_span)
582}
583
584pub fn unknown_function(name: &str, span: Span) -> Diagnostic {
586 Diagnostic::error(format!("unknown function `{name}`")).with_label(
587 "the WDL standard library does not have a function with this name",
588 span,
589 )
590}
591
592pub fn unsupported_function(minimum: SupportedVersion, name: &str, span: Span) -> Diagnostic {
594 Diagnostic::error(format!(
595 "this use of function `{name}` requires a minimum WDL version of {minimum}"
596 ))
597 .with_highlight(span)
598}
599
600pub fn too_few_arguments(name: &str, span: Span, minimum: usize, count: usize) -> Diagnostic {
602 Diagnostic::error(format!(
603 "function `{name}` requires at least {minimum} argument{s} but {count} {v} supplied",
604 s = if minimum == 1 { "" } else { "s" },
605 v = if count == 1 { "was" } else { "were" },
606 ))
607 .with_highlight(span)
608}
609
610pub fn too_many_arguments(
612 name: &str,
613 span: Span,
614 maximum: usize,
615 count: usize,
616 excessive: impl Iterator<Item = Span>,
617) -> Diagnostic {
618 let mut diagnostic = Diagnostic::error(format!(
619 "function `{name}` requires no more than {maximum} argument{s} but {count} {v} supplied",
620 s = if maximum == 1 { "" } else { "s" },
621 v = if count == 1 { "was" } else { "were" },
622 ))
623 .with_highlight(span);
624
625 for span in excessive {
626 diagnostic = diagnostic.with_label("this argument is unexpected", span);
627 }
628
629 diagnostic
630}
631
632pub fn argument_type_mismatch(name: &str, expected: &str, actual: &Type, span: Span) -> Diagnostic {
634 Diagnostic::error(format!(
635 "type mismatch: argument to function `{name}` expects type {expected}, but found type \
636 `{actual}`"
637 ))
638 .with_label(format!("this is type `{actual}`"), span)
639}
640
641pub fn ambiguous_argument(name: &str, span: Span, first: &str, second: &str) -> Diagnostic {
643 Diagnostic::error(format!(
644 "ambiguous call to function `{name}` with conflicting signatures `{first}` and `{second}`",
645 ))
646 .with_highlight(span)
647}
648
649pub fn index_type_mismatch(expected: &Type, actual: &Type, span: Span) -> Diagnostic {
651 Diagnostic::error(format!(
652 "type mismatch: expected index to be type `{expected}`, but found type `{actual}`"
653 ))
654 .with_label(format!("this is type `{actual}`"), span)
655}
656
657pub fn type_is_not_array(actual: &Type, span: Span) -> Diagnostic {
659 Diagnostic::error(format!(
660 "type mismatch: expected an array type, but found type `{actual}`"
661 ))
662 .with_label(format!("this is type `{actual}`"), span)
663}
664
665pub fn cannot_access(actual: &Type, actual_span: Span) -> Diagnostic {
667 Diagnostic::error(format!("cannot access type `{actual}`"))
668 .with_label(format!("this is type `{actual}`"), actual_span)
669}
670
671pub fn cannot_coerce_to_string(actual: &Type, span: Span) -> Diagnostic {
673 Diagnostic::error(format!("cannot coerce type `{actual}` to `String`"))
674 .with_label(format!("this is type `{actual}`"), span)
675}
676
677pub fn unknown_task_or_workflow(namespace: Option<Span>, name: &str, span: Span) -> Diagnostic {
679 let mut diagnostic =
680 Diagnostic::error(format!("unknown task or workflow `{name}`")).with_highlight(span);
681
682 if let Some(namespace) = namespace {
683 diagnostic = diagnostic.with_label(
684 format!("this namespace does not have a task or workflow named `{name}`"),
685 namespace,
686 );
687 }
688
689 diagnostic
690}
691
692pub fn unknown_call_io<T: TreeToken>(call: &CallType, name: &Ident<T>, io: Io) -> Diagnostic {
694 Diagnostic::error(format!(
695 "{kind} `{call}` does not have an {io} named `{name}`",
696 kind = call.kind(),
697 call = call.name(),
698 name = name.text(),
699 ))
700 .with_highlight(name.span())
701}
702
703pub fn unknown_task_io<T: TreeToken>(task_name: &str, name: &Ident<T>, io: Io) -> Diagnostic {
705 Diagnostic::error(format!(
706 "task `{task_name}` does not have an {io} named `{name}`",
707 name = name.text(),
708 ))
709 .with_highlight(name.span())
710}
711
712pub fn recursive_workflow_call(name: &str, span: Span) -> Diagnostic {
714 Diagnostic::error(format!("cannot recursively call workflow `{name}`")).with_highlight(span)
715}
716
717pub fn missing_call_input<T: TreeToken>(
719 kind: CallKind,
720 target: &Ident<T>,
721 input: &str,
722 nested_inputs_allowed: bool,
723) -> Diagnostic {
724 let message = format!(
725 "missing required call input `{input}` for {kind} `{target}`",
726 target = target.text(),
727 );
728
729 if nested_inputs_allowed {
730 Diagnostic::warning(message).with_highlight(target.span())
731 } else {
732 Diagnostic::error(message).with_highlight(target.span())
733 }
734}
735
736pub fn unused_import(name: &str, span: Span) -> Diagnostic {
738 Diagnostic::warning(format!("unused import namespace `{name}`"))
739 .with_rule(UNUSED_IMPORT_RULE_ID)
740 .with_highlight(span)
741}
742
743pub fn unused_input(name: &str, span: Span) -> Diagnostic {
745 Diagnostic::warning(format!("unused input `{name}`"))
746 .with_rule(UNUSED_INPUT_RULE_ID)
747 .with_highlight(span)
748}
749
750pub fn unused_declaration(name: &str, span: Span) -> Diagnostic {
752 Diagnostic::warning(format!("unused declaration `{name}`"))
753 .with_rule(UNUSED_DECL_RULE_ID)
754 .with_highlight(span)
755}
756
757pub fn unused_call(name: &str, span: Span) -> Diagnostic {
759 Diagnostic::warning(format!("unused call `{name}`"))
760 .with_rule(UNUSED_CALL_RULE_ID)
761 .with_highlight(span)
762}
763
764pub fn unnecessary_function_call(
766 name: &str,
767 span: Span,
768 label: &str,
769 label_span: Span,
770) -> Diagnostic {
771 Diagnostic::warning(format!("unnecessary call to function `{name}`"))
772 .with_rule(UNNECESSARY_FUNCTION_CALL)
773 .with_highlight(span)
774 .with_label(label.to_string(), label_span)
775}
776
777pub fn invalid_placeholder_option<N: TreeNode>(
780 ty: &Type,
781 span: Span,
782 option: &PlaceholderOption<N>,
783) -> Diagnostic {
784 let message = match option {
785 PlaceholderOption::Sep(_) => format!(
786 "type mismatch for placeholder option `sep`: expected type `Array[P]` where P: any \
787 primitive type, but found `{ty}`"
788 ),
789 PlaceholderOption::Default(_) => format!(
790 "type mismatch for placeholder option `default`: expected any primitive type, but \
791 found `{ty}`"
792 ),
793 PlaceholderOption::TrueFalse(_) => format!(
794 "type mismatch for placeholder option `true/false`: expected type `Boolean`, but \
795 found `{ty}`"
796 ),
797 };
798
799 Diagnostic::error(message).with_label(format!("this is type `{ty}`"), span)
800}
801
802pub fn invalid_regex_pattern(
804 function: &str,
805 pattern: &str,
806 error: ®ex::Error,
807 span: Span,
808) -> Diagnostic {
809 Diagnostic::error(format!(
810 "invalid regular expression `{pattern}` used in function `{function}`: {error}"
811 ))
812 .with_label("invalid regular expression", span)
813}