use std::fmt;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Ident;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::TreeNode;
use wdl_ast::TreeToken;
use wdl_ast::Version;
use wdl_ast::v1::PlaceholderOption;
use crate::UNNECESSARY_FUNCTION_CALL;
use crate::UNUSED_CALL_RULE_ID;
use crate::UNUSED_DECL_RULE_ID;
use crate::UNUSED_IMPORT_RULE_ID;
use crate::UNUSED_INPUT_RULE_ID;
use crate::types::CallKind;
use crate::types::CallType;
use crate::types::Type;
use crate::types::display_types;
use crate::types::v1::ComparisonOperator;
use crate::types::v1::NumericOperator;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Io {
Input,
Output,
}
impl fmt::Display for Io {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input => write!(f, "input"),
Self::Output => write!(f, "output"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Context {
Namespace(Span),
Workflow(Span),
Task(Span),
Struct(Span),
StructMember(Span),
Enum(Span),
EnumVariant(Span),
Name(NameContext),
}
impl Context {
fn span(&self) -> Span {
match self {
Self::Namespace(s) => *s,
Self::Workflow(s) => *s,
Self::Task(s) => *s,
Self::Struct(s) => *s,
Self::StructMember(s) => *s,
Self::Enum(s) => *s,
Self::EnumVariant(s) => *s,
Self::Name(n) => n.span(),
}
}
}
impl fmt::Display for Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Namespace(_) => write!(f, "namespace"),
Self::Workflow(_) => write!(f, "workflow"),
Self::Task(_) => write!(f, "task"),
Self::Struct(_) => write!(f, "struct"),
Self::StructMember(_) => write!(f, "struct member"),
Self::Enum(_) => write!(f, "enum"),
Self::EnumVariant(_) => write!(f, "enum variant"),
Self::Name(n) => n.fmt(f),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NameContext {
Input(Span),
Output(Span),
Decl(Span),
Call(Span),
ScatterVariable(Span),
}
impl NameContext {
pub fn span(&self) -> Span {
match self {
Self::Input(s) => *s,
Self::Output(s) => *s,
Self::Decl(s) => *s,
Self::Call(s) => *s,
Self::ScatterVariable(s) => *s,
}
}
}
impl fmt::Display for NameContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input(_) => write!(f, "input"),
Self::Output(_) => write!(f, "output"),
Self::Decl(_) => write!(f, "declaration"),
Self::Call(_) => write!(f, "call"),
Self::ScatterVariable(_) => write!(f, "scatter variable"),
}
}
}
impl From<NameContext> for Context {
fn from(context: NameContext) -> Self {
Self::Name(context)
}
}
pub fn name_conflict(name: &str, conflicting: Context, first: Context) -> Diagnostic {
Diagnostic::error(format!("conflicting {conflicting} name `{name}`"))
.with_label(
format!("this {conflicting} conflicts with a previously used name"),
conflicting.span(),
)
.with_label(
format!("the {first} with the conflicting name is here"),
first.span(),
)
}
pub fn cannot_index(actual: &Type, span: Span) -> Diagnostic {
Diagnostic::error("indexing is only allowed on `Array` and `Map` types")
.with_label(format!("this is {actual:#}"), span)
}
pub fn unknown_name(name: &str, span: Span) -> Diagnostic {
let message = match name {
"task" => "the `task` variable may only be used within a task command section or task \
output section using WDL 1.2 or later, or within a task requirements, task \
hints, or task runtime section using WDL 1.3 or later"
.to_string(),
_ => format!("unknown name `{name}`"),
};
Diagnostic::error(message).with_highlight(span)
}
pub fn self_referential(name: &str, span: Span, reference: Span) -> Diagnostic {
Diagnostic::error(format!("declaration of `{name}` is self-referential"))
.with_label("self-reference is here", reference)
.with_highlight(span)
}
pub fn task_reference_cycle(
from: &impl fmt::Display,
from_span: Span,
to: &str,
to_span: Span,
) -> Diagnostic {
Diagnostic::error("a name reference cycle was detected")
.with_label(
format!("ensure this expression does not directly or indirectly refer to {from}"),
to_span,
)
.with_label(format!("a reference back to `{to}` is here"), from_span)
}
pub fn workflow_reference_cycle(
from: &impl fmt::Display,
from_span: Span,
to: &str,
to_span: Span,
) -> Diagnostic {
Diagnostic::error("a name reference cycle was detected")
.with_label(format!("this name depends on {from}"), to_span)
.with_label(format!("a reference back to `{to}` is here"), from_span)
}
pub fn call_conflict<T: TreeToken>(
name: &Ident<T>,
first: NameContext,
suggest_fix: bool,
) -> Diagnostic {
let diagnostic = Diagnostic::error(format!(
"conflicting call name `{name}`",
name = name.text()
))
.with_label(
"this call name conflicts with a previously used name",
name.span(),
)
.with_label(
format!("the {first} with the conflicting name is here"),
first.span(),
);
if suggest_fix {
diagnostic.with_fix("add an `as` clause to the call to specify a different name")
} else {
diagnostic
}
}
pub fn namespace_conflict(
name: &str,
conflicting: Span,
first: Span,
suggest_fix: bool,
) -> Diagnostic {
let diagnostic = Diagnostic::error(format!("conflicting import namespace `{name}`"))
.with_label("this conflicts with another import namespace", conflicting)
.with_label(
"the conflicting import namespace was introduced here",
first,
);
if suggest_fix {
diagnostic.with_fix("add an `as` clause to the import to specify a namespace")
} else {
diagnostic
}
}
pub fn unknown_namespace<T: TreeToken>(ns: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!("unknown namespace `{ns}`", ns = ns.text())).with_highlight(ns.span())
}
pub fn only_one_namespace(span: Span) -> Diagnostic {
Diagnostic::error("only one namespace may be specified in a call statement")
.with_highlight(span)
}
pub fn import_cycle(span: Span) -> Diagnostic {
Diagnostic::error("import introduces a dependency cycle")
.with_label("this import has been skipped to break the cycle", span)
}
pub fn import_failure(uri: &str, error: &anyhow::Error, span: Span) -> Diagnostic {
Diagnostic::error(format!("failed to import `{uri}`: {error:#}")).with_highlight(span)
}
pub fn incompatible_import(
import_version: &str,
import_span: Span,
importer_version: &Version,
) -> Diagnostic {
Diagnostic::error("imported document has incompatible version")
.with_label(
format!("the imported document is version `{import_version}`"),
import_span,
)
.with_label(
format!(
"the importing document is version `{version}`",
version = importer_version.text()
),
importer_version.span(),
)
}
pub fn import_missing_version(span: Span) -> Diagnostic {
Diagnostic::error("imported document is missing a version statement").with_highlight(span)
}
pub fn invalid_relative_import(error: &url::ParseError, span: Span) -> Diagnostic {
Diagnostic::error(format!("{error:#}")).with_highlight(span)
}
pub fn struct_not_in_document<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"a struct named `{name}` does not exist in the imported document",
name = name.text()
))
.with_label("this struct does not exist", name.span())
}
pub fn imported_struct_conflict(
name: &str,
conflicting: Span,
first: Span,
suggest_fix: bool,
) -> Diagnostic {
let diagnostic = Diagnostic::error(format!("conflicting struct name `{name}`"))
.with_label(
"this import introduces a conflicting definition",
conflicting,
)
.with_label("the first definition was introduced by this import", first);
if suggest_fix {
diagnostic.with_fix("add an `alias` clause to the import to specify a different name")
} else {
diagnostic
}
}
pub fn struct_conflicts_with_import(name: &str, conflicting: Span, import: Span) -> Diagnostic {
Diagnostic::error(format!("conflicting struct name `{name}`"))
.with_label("this name conflicts with an imported struct", conflicting)
.with_label("the import that introduced the struct is here", import)
.with_fix(
"either rename the struct or use an `alias` clause on the import with a different name",
)
}
pub fn imported_enum_conflict(
name: &str,
conflicting: Span,
first: Span,
suggest_fix: bool,
) -> Diagnostic {
let diagnostic = Diagnostic::error(format!("conflicting enum name `{name}`"))
.with_label(
"this import introduces a conflicting definition",
conflicting,
)
.with_label("the first definition was introduced by this import", first);
if suggest_fix {
diagnostic.with_fix("add an `alias` clause to the import to specify a different name")
} else {
diagnostic
}
}
pub fn enum_conflicts_with_import(name: &str, conflicting: Span, import: Span) -> Diagnostic {
Diagnostic::error(format!("conflicting enum name `{name}`"))
.with_label("this name conflicts with an imported enum", conflicting)
.with_label("the import that introduced the enum is here", import)
.with_fix(
"either rename the enum or use an `alias` clause on the import with a different name",
)
}
pub fn duplicate_workflow<T: TreeToken>(name: &Ident<T>, first: Span) -> Diagnostic {
Diagnostic::error(format!(
"cannot define workflow `{name}` as only one workflow is allowed per source file",
name = name.text(),
))
.with_label("consider moving this workflow to a new file", name.span())
.with_label("first workflow is defined here", first)
}
pub fn recursive_struct(name: &str, span: Span, member: Span) -> Diagnostic {
Diagnostic::error(format!("struct `{name}` has a recursive definition"))
.with_highlight(span)
.with_label("this struct member participates in the recursion", member)
}
pub fn unknown_type(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(format!("unknown type name `{name}`")).with_highlight(span)
}
pub fn type_mismatch(
expected: &Type,
expected_span: Span,
actual: &Type,
actual_span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected {expected:#}, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
.with_label(format!("this expects {expected:#}"), expected_span)
}
pub fn non_empty_array_assignment(expected_span: Span, actual_span: Span) -> Diagnostic {
Diagnostic::error("cannot assign an empty array to a non-empty array type")
.with_label("this is an empty array", actual_span)
.with_label("this expects a non-empty array", expected_span)
}
pub fn call_input_type_mismatch<T: TreeToken>(
name: &Ident<T>,
expected: &Type,
actual: &Type,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected {expected:#}, but found {actual:#}",
))
.with_label(
format!(
"input `{name}` is {expected:#}, but name `{name}` is {actual:#}",
name = name.text(),
),
name.span(),
)
}
pub fn no_common_type(
expected: &Type,
expected_span: Span,
actual: &Type,
actual_span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: a type common to both {expected:#} and {actual:#} does not exist"
))
.with_label(format!("this is {actual:#}"), actual_span)
.with_label(
format!("this and all prior elements had a common {expected:#}"),
expected_span,
)
}
pub fn multiple_type_mismatch(
expected: &[Type],
expected_span: Span,
actual: &Type,
actual_span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected {expected:#}, but found {actual:#}",
expected = display_types(expected),
))
.with_label(format!("this is {actual:#}"), actual_span)
.with_label(
format!(
"this expects {expected:#}",
expected = display_types(expected)
),
expected_span,
)
}
pub fn not_a_task_member<T: TreeToken>(member: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"the `task` variable does not have a member named `{member}`",
member = member.text()
))
.with_highlight(member.span())
}
pub fn not_a_previous_task_data_member<T: TreeToken>(member: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"`task.previous` does not have a member named `{member}`",
member = member.text()
))
.with_highlight(member.span())
}
pub fn not_a_struct<T: TreeToken>(member: &Ident<T>, input: bool) -> Diagnostic {
Diagnostic::error(format!(
"{kind} `{member}` is not a struct",
kind = if input { "input" } else { "struct member" },
member = member.text()
))
.with_highlight(member.span())
}
pub fn not_a_struct_member<T: TreeToken>(name: &str, member: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"struct `{name}` does not have a member named `{member}`",
member = member.text()
))
.with_highlight(member.span())
}
pub fn not_an_enum_variant<T: TreeToken>(name: &str, variant: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"enum `{name}` does not have a variant named `{variant}`",
variant = variant.text()
))
.with_highlight(variant.span())
}
pub fn non_literal_enum_value(span: Span) -> Diagnostic {
Diagnostic::error("enum variant value must be a literal expression")
.with_highlight(span)
.with_fix(
"enum values must be literal expressions only (string literals, numeric literals, \
collection literals, or struct literals); string interpolation, variable references, \
and computed expressions are not allowed",
)
}
pub fn not_a_pair_accessor<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!(
"cannot access a pair with name `{name}`",
name = name.text()
))
.with_highlight(name.span())
.with_fix("use `left` or `right` to access a pair")
}
pub fn missing_struct_members<T: TreeToken>(
name: &Ident<T>,
count: usize,
members: &str,
) -> Diagnostic {
Diagnostic::error(format!(
"struct `{name}` requires a value for member{s} {members}",
name = name.text(),
s = if count > 1 { "s" } else { "" },
))
.with_highlight(name.span())
}
pub fn map_key_not_primitive(span: Span, actual: &Type) -> Diagnostic {
Diagnostic::error("expected map key to be a non-optional primitive type")
.with_highlight(span)
.with_label(format!("this is {actual:#}"), span)
}
pub fn if_conditional_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected `if` conditional expression to be type `Boolean`, but found \
{actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn else_if_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"`else if` conditional clauses are not supported in WDL v{version}"
))
.with_label("this `else if` is not supported", span)
.with_fix("use WDL v1.3 or higher to use `else if` conditional clauses")
}
pub fn else_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"`else` conditional clauses are not supported in WDL v{version}"
))
.with_label("this `else` is not supported", span)
.with_fix("use WDL v1.3 or higher to use `else` conditional clauses")
}
pub fn enum_not_supported(version: SupportedVersion, span: Span) -> Diagnostic {
Diagnostic::error(format!("enums are not supported in WDL v{version}"))
.with_label("this enum is not supported", span)
.with_fix("use WDL v1.3 or higher to use enums")
}
pub fn logical_not_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected `logical not` operand to be type `Boolean`, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn negation_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected negation operand to be type `Int` or `Float`, but found \
{actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn logical_or_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected `logical or` operand to be type `Boolean`, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn logical_and_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected `logical and` operand to be type `Boolean`, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn comparison_mismatch(
op: ComparisonOperator,
span: Span,
lhs: &Type,
lhs_span: Span,
rhs: &Type,
rhs_span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: operator `{op}` cannot compare {lhs:#} to {rhs:#}"
))
.with_highlight(span)
.with_label(format!("this is {lhs:#}"), lhs_span)
.with_label(format!("this is {rhs:#}"), rhs_span)
}
pub fn numeric_mismatch(
op: NumericOperator,
span: Span,
lhs: &Type,
lhs_span: Span,
rhs: &Type,
rhs_span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: {op} operator is not supported for {lhs:#} and {rhs:#}"
))
.with_highlight(span)
.with_label(format!("this is {lhs:#}"), lhs_span)
.with_label(format!("this is {rhs:#}"), rhs_span)
}
pub fn string_concat_mismatch(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: string concatenation is not supported for {actual:#}"
))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn unknown_function(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(format!("unknown function `{name}`")).with_label(
"the WDL standard library does not have a function with this name",
span,
)
}
pub fn unsupported_function(minimum: SupportedVersion, name: &str, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"this use of function `{name}` requires a minimum WDL version of {minimum}"
))
.with_highlight(span)
}
pub fn too_few_arguments(name: &str, span: Span, minimum: usize, count: usize) -> Diagnostic {
Diagnostic::error(format!(
"function `{name}` requires at least {minimum} argument{s} but {count} {v} supplied",
s = if minimum == 1 { "" } else { "s" },
v = if count == 1 { "was" } else { "were" },
))
.with_highlight(span)
}
pub fn too_many_arguments(
name: &str,
span: Span,
maximum: usize,
count: usize,
excessive: impl Iterator<Item = Span>,
) -> Diagnostic {
let mut diagnostic = Diagnostic::error(format!(
"function `{name}` requires no more than {maximum} argument{s} but {count} {v} supplied",
s = if maximum == 1 { "" } else { "s" },
v = if count == 1 { "was" } else { "were" },
))
.with_highlight(span);
for span in excessive {
diagnostic = diagnostic.with_label("this argument is unexpected", span);
}
diagnostic
}
pub fn argument_type_mismatch(name: &str, expected: &str, actual: &Type, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: argument to function `{name}` expects {expected}, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), span)
}
pub fn ambiguous_argument(name: &str, span: Span, first: &str, second: &str) -> Diagnostic {
Diagnostic::error(format!(
"ambiguous call to function `{name}` with conflicting signatures `{first}` and `{second}`",
))
.with_highlight(span)
}
pub fn index_type_mismatch(expected: &Type, actual: &Type, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected index to be {expected:#}, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), span)
}
pub fn type_is_not_array(actual: &Type, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"type mismatch: expected an array type, but found {actual:#}"
))
.with_label(format!("this is {actual:#}"), span)
}
pub fn cannot_access(actual: &Type, actual_span: Span) -> Diagnostic {
Diagnostic::error(format!("cannot access {actual:#}"))
.with_label(format!("this is {actual:#}"), actual_span)
}
pub fn cannot_coerce_to_string(actual: &Type, span: Span) -> Diagnostic {
Diagnostic::error(format!("cannot coerce {actual:#} to type `String`"))
.with_label(format!("this is {actual:#}"), span)
}
pub fn unknown_task_or_workflow(namespace: Option<Span>, name: &str, span: Span) -> Diagnostic {
let mut diagnostic =
Diagnostic::error(format!("unknown task or workflow `{name}`")).with_highlight(span);
if let Some(namespace) = namespace {
diagnostic = diagnostic.with_label(
format!("this namespace does not have a task or workflow named `{name}`"),
namespace,
);
}
diagnostic
}
pub fn unknown_call_io<T: TreeToken>(call: &CallType, name: &Ident<T>, io: Io) -> Diagnostic {
Diagnostic::error(format!(
"{kind} `{call}` does not have an {io} named `{name}`",
kind = call.kind(),
call = call.name(),
name = name.text(),
))
.with_highlight(name.span())
}
pub fn unknown_task_io<T: TreeToken>(task_name: &str, name: &Ident<T>, io: Io) -> Diagnostic {
Diagnostic::error(format!(
"task `{task_name}` does not have an {io} named `{name}`",
name = name.text(),
))
.with_highlight(name.span())
}
pub fn recursive_workflow_call(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(format!("cannot recursively call workflow `{name}`")).with_highlight(span)
}
pub fn missing_call_input<T: TreeToken>(
kind: CallKind,
target: &Ident<T>,
input: &str,
nested_inputs_allowed: bool,
) -> Diagnostic {
let message = format!(
"missing required call input `{input}` for {kind} `{target}`",
target = target.text(),
);
if nested_inputs_allowed {
Diagnostic::warning(message).with_highlight(target.span())
} else {
Diagnostic::error(message).with_highlight(target.span())
}
}
pub fn unused_import(name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!("unused import namespace `{name}`"))
.with_rule(UNUSED_IMPORT_RULE_ID)
.with_highlight(span)
}
pub fn unused_input(name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!("unused input `{name}`"))
.with_rule(UNUSED_INPUT_RULE_ID)
.with_highlight(span)
}
pub fn unused_declaration(name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!("unused declaration `{name}`"))
.with_rule(UNUSED_DECL_RULE_ID)
.with_highlight(span)
}
pub fn unused_call(name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!("unused call `{name}`"))
.with_rule(UNUSED_CALL_RULE_ID)
.with_highlight(span)
}
pub fn unnecessary_function_call(
name: &str,
span: Span,
label: &str,
label_span: Span,
) -> Diagnostic {
Diagnostic::warning(format!("unnecessary call to function `{name}`"))
.with_rule(UNNECESSARY_FUNCTION_CALL)
.with_highlight(span)
.with_label(label.to_string(), label_span)
}
pub fn invalid_placeholder_option<N: TreeNode>(
ty: &Type,
span: Span,
option: &PlaceholderOption<N>,
) -> Diagnostic {
let message = match option {
PlaceholderOption::Sep(_) => format!(
"type mismatch for placeholder option `sep`: expected type `Array[P]` where P: any \
primitive type, but found {ty:#}"
),
PlaceholderOption::Default(_) => format!(
"type mismatch for placeholder option `default`: expected any primitive type, but \
found {ty:#}"
),
PlaceholderOption::TrueFalse(_) => format!(
"type mismatch for placeholder option `true/false`: expected type `Boolean`, but \
found {ty:#}"
),
};
Diagnostic::error(message).with_label(format!("this is {ty:#}"), span)
}
pub fn invalid_regex_pattern(
function: &str,
pattern: &str,
error: ®ex::Error,
span: Span,
) -> Diagnostic {
Diagnostic::error(format!(
"invalid regular expression `{pattern}` used in function `{function}`: {error}"
))
.with_label("invalid regular expression", span)
}
pub fn not_a_custom_type<T: TreeToken>(name: &Ident<T>) -> Diagnostic {
Diagnostic::error(format!("`{}` is not a custom type", name.text())).with_label(
"only struct and enum types can be referenced as values",
name.span(),
)
}
pub fn no_common_inferred_type_for_enum(
enum_name: &str,
common_type: &Type,
common_span: Span,
discordant_type: &Type,
discordant_span: Span,
) -> Diagnostic {
Diagnostic::error(format!("cannot infer a common type for enum `{enum_name}`"))
.with_label(
format!(
"this is the first variant with {discordant_type:#} that has no common type with \
{common_type:#}"
),
discordant_span,
)
.with_label(
format!("this is the last variant with a common {common_type:#}"),
common_span,
)
}
pub fn enum_variant_does_not_coerce_to_type(
enum_name: &str,
enum_span: Span,
variant_name: &str,
variant_span: Span,
expected: &Type,
actual: &Type,
) -> Diagnostic {
Diagnostic::error(format!(
"cannot coerce variant `{variant_name}` in enum `{enum_name}` from {actual:#} to \
{expected:#}"
))
.with_label(format!("this is the `{enum_name}` enum"), enum_span)
.with_label(
format!("this is the `{variant_name}` variant"),
variant_span,
)
.with_fix(format!(
"change the value to something that coerces to {expected:#} or explicitly set the enum's \
inner type"
))
}