use std::fmt;
use std::fmt::Write;
use std::sync::LazyLock;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Ident;
use wdl_ast::Severity;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::TreeNode;
use wdl_ast::TreeToken;
use wdl_ast::v1;
use wdl_ast::v1::AccessExpr;
use wdl_ast::v1::CallExpr;
use wdl_ast::v1::Expr;
use wdl_ast::v1::IfExpr;
use wdl_ast::v1::IndexExpr;
use wdl_ast::v1::LiteralArray;
use wdl_ast::v1::LiteralExpr;
use wdl_ast::v1::LiteralHints;
use wdl_ast::v1::LiteralInput;
use wdl_ast::v1::LiteralMap;
use wdl_ast::v1::LiteralMapItem;
use wdl_ast::v1::LiteralObject;
use wdl_ast::v1::LiteralOutput;
use wdl_ast::v1::LiteralPair;
use wdl_ast::v1::LiteralStruct;
use wdl_ast::v1::LogicalAndExpr;
use wdl_ast::v1::LogicalNotExpr;
use wdl_ast::v1::LogicalOrExpr;
use wdl_ast::v1::NegationExpr;
use wdl_ast::v1::Placeholder;
use wdl_ast::v1::PlaceholderOption;
use wdl_ast::v1::StringPart;
use wdl_ast::v1::TASK_FIELD_ATTEMPT;
use wdl_ast::v1::TASK_FIELD_CONTAINER;
use wdl_ast::v1::TASK_FIELD_CPU;
use wdl_ast::v1::TASK_FIELD_DISKS;
use wdl_ast::v1::TASK_FIELD_END_TIME;
use wdl_ast::v1::TASK_FIELD_EXT;
use wdl_ast::v1::TASK_FIELD_FPGA;
use wdl_ast::v1::TASK_FIELD_GPU;
use wdl_ast::v1::TASK_FIELD_ID;
use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
use wdl_ast::v1::TASK_FIELD_MEMORY;
use wdl_ast::v1::TASK_FIELD_META;
use wdl_ast::v1::TASK_FIELD_NAME;
use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
use wdl_ast::v1::TASK_FIELD_PREVIOUS;
use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
use wdl_ast::v1::TASK_HINT_CACHEABLE;
use wdl_ast::v1::TASK_HINT_DISKS;
use wdl_ast::v1::TASK_HINT_FPGA;
use wdl_ast::v1::TASK_HINT_GPU;
use wdl_ast::v1::TASK_HINT_INPUTS;
use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL;
use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS;
use wdl_ast::v1::TASK_HINT_MAX_CPU;
use wdl_ast::v1::TASK_HINT_MAX_CPU_ALIAS;
use wdl_ast::v1::TASK_HINT_MAX_MEMORY;
use wdl_ast::v1::TASK_HINT_MAX_MEMORY_ALIAS;
use wdl_ast::v1::TASK_HINT_OUTPUTS;
use wdl_ast::v1::TASK_HINT_SHORT_TASK;
use wdl_ast::v1::TASK_HINT_SHORT_TASK_ALIAS;
use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER;
use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER_ALIAS;
use wdl_ast::v1::TASK_REQUIREMENT_CPU;
use wdl_ast::v1::TASK_REQUIREMENT_DISKS;
use wdl_ast::v1::TASK_REQUIREMENT_FPGA;
use wdl_ast::v1::TASK_REQUIREMENT_GPU;
use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES;
use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES_ALIAS;
use wdl_ast::v1::TASK_REQUIREMENT_MEMORY;
use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES;
use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES_ALIAS;
use wdl_ast::version::V1;
use super::ArrayType;
use super::CompoundType;
use super::HiddenType;
use super::MapType;
use super::Optional;
use super::PairType;
use super::PrimitiveType;
use super::StructType;
use super::Type;
use super::TypeNameResolver;
use crate::Exceptable;
use crate::UNNECESSARY_FUNCTION_CALL;
use crate::config::DiagnosticsConfig;
use crate::diagnostics::Io;
use crate::diagnostics::ambiguous_argument;
use crate::diagnostics::argument_type_mismatch;
use crate::diagnostics::cannot_access;
use crate::diagnostics::cannot_coerce_to_string;
use crate::diagnostics::cannot_index;
use crate::diagnostics::comparison_mismatch;
use crate::diagnostics::if_conditional_mismatch;
use crate::diagnostics::index_type_mismatch;
use crate::diagnostics::invalid_placeholder_option;
use crate::diagnostics::invalid_regex_pattern;
use crate::diagnostics::logical_and_mismatch;
use crate::diagnostics::logical_not_mismatch;
use crate::diagnostics::logical_or_mismatch;
use crate::diagnostics::map_key_not_primitive;
use crate::diagnostics::missing_struct_members;
use crate::diagnostics::multiple_type_mismatch;
use crate::diagnostics::negation_mismatch;
use crate::diagnostics::no_common_type;
use crate::diagnostics::not_a_pair_accessor;
use crate::diagnostics::not_a_previous_task_data_member;
use crate::diagnostics::not_a_struct;
use crate::diagnostics::not_a_struct_member;
use crate::diagnostics::not_a_task_member;
use crate::diagnostics::numeric_mismatch;
use crate::diagnostics::string_concat_mismatch;
use crate::diagnostics::too_few_arguments;
use crate::diagnostics::too_many_arguments;
use crate::diagnostics::type_mismatch;
use crate::diagnostics::unknown_call_io;
use crate::diagnostics::unknown_function;
use crate::diagnostics::unknown_task_io;
use crate::diagnostics::unnecessary_function_call;
use crate::diagnostics::unsupported_function;
use crate::document::Task;
use crate::stdlib::FunctionBindError;
use crate::stdlib::MAX_PARAMETERS;
use crate::stdlib::STDLIB;
use crate::types::Coercible;
use crate::types::CustomType;
pub fn task_member_type_pre_evaluation(name: &str) -> Option<Type> {
match name {
TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
TASK_FIELD_ATTEMPT => Some(PrimitiveType::Integer.into()),
TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
TASK_FIELD_PREVIOUS => Some(Type::Hidden(HiddenType::PreviousTaskData)),
_ => None,
}
}
pub fn task_member_type_post_evaluation(version: SupportedVersion, name: &str) -> Option<Type> {
match name {
TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
TASK_FIELD_CPU => Some(PrimitiveType::Float.into()),
TASK_FIELD_MEMORY | TASK_FIELD_ATTEMPT => Some(PrimitiveType::Integer.into()),
TASK_FIELD_GPU | TASK_FIELD_FPGA => Some(STDLIB.array_string_type().clone().into()),
TASK_FIELD_DISKS => Some(STDLIB.map_string_int_type().clone().into()),
TASK_FIELD_END_TIME | TASK_FIELD_RETURN_CODE => {
Some(Type::from(PrimitiveType::Integer).optional())
}
TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
Some(PrimitiveType::Integer.into())
}
TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
Some(Type::Hidden(HiddenType::PreviousTaskData))
}
_ => None,
}
}
pub fn previous_task_data_member_type(name: &str) -> Option<Type> {
match name {
TASK_FIELD_MEMORY => Some(Type::from(PrimitiveType::Integer).optional()),
TASK_FIELD_CPU => Some(Type::from(PrimitiveType::Float).optional()),
TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
TASK_FIELD_GPU | TASK_FIELD_FPGA => {
Some(Type::from(STDLIB.array_string_type().clone()).optional())
}
TASK_FIELD_DISKS => Some(Type::from(STDLIB.map_string_int_type().clone()).optional()),
TASK_FIELD_MAX_RETRIES => Some(Type::from(PrimitiveType::Integer).optional()),
_ => None,
}
}
pub fn task_requirement_types(version: SupportedVersion, name: &str) -> Option<&'static [Type]> {
static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
Box::new([
PrimitiveType::String.into(),
STDLIB.array_string_type().clone().into(),
])
});
const CPU_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::Float, false),
];
const MEMORY_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::String, false),
];
const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
Box::new([
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
STDLIB.array_string_type().clone().into(),
])
});
const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
static RETURN_CODES_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
Box::new([
PrimitiveType::Integer.into(),
PrimitiveType::String.into(),
STDLIB.array_int_type().clone().into(),
])
});
match name {
TASK_REQUIREMENT_CONTAINER | TASK_REQUIREMENT_CONTAINER_ALIAS => Some(&CONTAINER_TYPES),
TASK_REQUIREMENT_CPU => Some(CPU_TYPES),
TASK_REQUIREMENT_DISKS => Some(&DISKS_TYPES),
TASK_REQUIREMENT_GPU => Some(GPU_TYPES),
TASK_REQUIREMENT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
TASK_REQUIREMENT_MAX_RETRIES if version >= SupportedVersion::V1(V1::Two) => {
Some(MAX_RETRIES_TYPES)
}
TASK_REQUIREMENT_MAX_RETRIES_ALIAS => Some(MAX_RETRIES_TYPES),
TASK_REQUIREMENT_MEMORY => Some(MEMORY_TYPES),
TASK_REQUIREMENT_RETURN_CODES if version >= SupportedVersion::V1(V1::Two) => {
Some(&RETURN_CODES_TYPES)
}
TASK_REQUIREMENT_RETURN_CODES_ALIAS => Some(&RETURN_CODES_TYPES),
_ => None,
}
}
pub fn task_hint_types(
version: SupportedVersion,
name: &str,
use_hidden_types: bool,
) -> Option<&'static [Type]> {
static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
Box::new([
PrimitiveType::String.into(),
STDLIB.map_string_string_type().clone().into(),
])
});
const FPGA_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::String, false),
];
const GPU_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::String, false),
];
const INPUTS_TYPES: &[Type] = &[Type::Object];
const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Input)];
const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
const MAX_CPU_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::Float, false),
];
const MAX_MEMORY_TYPES: &[Type] = &[
Type::Primitive(PrimitiveType::Integer, false),
Type::Primitive(PrimitiveType::String, false),
];
const OUTPUTS_TYPES: &[Type] = &[Type::Object];
const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Output)];
const SHORT_TASK_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
const CACHEABLE_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
match name {
TASK_HINT_DISKS => Some(&DISKS_TYPES),
TASK_HINT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
TASK_HINT_GPU => Some(GPU_TYPES),
TASK_HINT_INPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
Some(INPUTS_HIDDEN_TYPES)
}
TASK_HINT_INPUTS => Some(INPUTS_TYPES),
TASK_HINT_LOCALIZATION_OPTIONAL if version >= SupportedVersion::V1(V1::Two) => {
Some(LOCALIZATION_OPTIONAL_TYPES)
}
TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS => Some(LOCALIZATION_OPTIONAL_TYPES),
TASK_HINT_MAX_CPU if version >= SupportedVersion::V1(V1::Two) => Some(MAX_CPU_TYPES),
TASK_HINT_MAX_CPU_ALIAS => Some(MAX_CPU_TYPES),
TASK_HINT_MAX_MEMORY if version >= SupportedVersion::V1(V1::Two) => Some(MAX_MEMORY_TYPES),
TASK_HINT_MAX_MEMORY_ALIAS => Some(MAX_MEMORY_TYPES),
TASK_HINT_OUTPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
Some(OUTPUTS_HIDDEN_TYPES)
}
TASK_HINT_OUTPUTS => Some(OUTPUTS_TYPES),
TASK_HINT_SHORT_TASK if version >= SupportedVersion::V1(V1::Two) => Some(SHORT_TASK_TYPES),
TASK_HINT_SHORT_TASK_ALIAS => Some(SHORT_TASK_TYPES),
TASK_HINT_CACHEABLE => Some(CACHEABLE_TYPES),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComparisonOperator {
Equality,
Inequality,
Less,
LessEqual,
Greater,
GreaterEqual,
}
impl fmt::Display for ComparisonOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Equality => "==",
Self::Inequality => "!=",
Self::Less => "<",
Self::LessEqual => "<=",
Self::Greater => ">",
Self::GreaterEqual => ">=",
}
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericOperator {
Addition,
Subtraction,
Multiplication,
Division,
Modulo,
Exponentiation,
}
impl fmt::Display for NumericOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Addition => "addition",
Self::Subtraction => "subtraction",
Self::Multiplication => "multiplication",
Self::Division => "division",
Self::Modulo => "remainder",
Self::Exponentiation => "exponentiation",
}
)
}
}
#[derive(Debug)]
pub struct AstTypeConverter<R>(R);
impl<R> AstTypeConverter<R>
where
R: TypeNameResolver,
{
pub fn new(resolver: R) -> Self {
Self(resolver)
}
pub fn convert_type<N: TreeNode>(&mut self, ty: &v1::Type<N>) -> Result<Type, Diagnostic> {
let optional = ty.is_optional();
let ty: Type = match ty {
v1::Type::Map(ty) => {
let ty = self.convert_map_type(ty)?;
ty.into()
}
v1::Type::Array(ty) => {
let ty = self.convert_array_type(ty)?;
ty.into()
}
v1::Type::Pair(ty) => {
let ty = self.convert_pair_type(ty)?;
ty.into()
}
v1::Type::Object(_) => Type::Object,
v1::Type::Ref(r) => {
let name = r.name();
self.0.resolve(name.text(), name.span())?
}
v1::Type::Primitive(ty) => Type::Primitive(ty.kind().into(), false),
};
if optional { Ok(ty.optional()) } else { Ok(ty) }
}
pub fn convert_array_type<N: TreeNode>(
&mut self,
ty: &v1::ArrayType<N>,
) -> Result<ArrayType, Diagnostic> {
let element_type = self.convert_type(&ty.element_type())?;
if ty.is_non_empty() {
Ok(ArrayType::non_empty(element_type))
} else {
Ok(ArrayType::new(element_type))
}
}
pub fn convert_pair_type<N: TreeNode>(
&mut self,
ty: &v1::PairType<N>,
) -> Result<PairType, Diagnostic> {
let (left_type, right_type) = ty.types();
Ok(PairType::new(
self.convert_type(&left_type)?,
self.convert_type(&right_type)?,
))
}
pub fn convert_map_type<N: TreeNode>(
&mut self,
ty: &v1::MapType<N>,
) -> Result<MapType, Diagnostic> {
let (key_type, value_type) = ty.types();
let key_type =
Type::Primitive(PrimitiveType::from(key_type.kind()), key_type.is_optional());
if key_type.is_optional() {
return Err(map_key_not_primitive(ty.types().0.span(), &key_type));
}
Ok(MapType::new(key_type, self.convert_type(&value_type)?))
}
pub fn convert_struct_type<N: TreeNode>(
&mut self,
definition: &v1::StructDefinition<N>,
) -> Result<StructType, Diagnostic> {
Ok(StructType::new(
definition.name().text().to_string(),
definition
.members()
.map(|d| Ok((d.name().text().to_string(), self.convert_type(&d.ty())?)))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
impl From<v1::PrimitiveTypeKind> for PrimitiveType {
fn from(value: v1::PrimitiveTypeKind) -> Self {
match value {
v1::PrimitiveTypeKind::Boolean => Self::Boolean,
v1::PrimitiveTypeKind::Integer => Self::Integer,
v1::PrimitiveTypeKind::Float => Self::Float,
v1::PrimitiveTypeKind::String => Self::String,
v1::PrimitiveTypeKind::File => Self::File,
v1::PrimitiveTypeKind::Directory => Self::Directory,
}
}
}
pub trait EvaluationContext {
fn version(&self) -> SupportedVersion;
fn resolve_name(&self, name: &str, span: Span) -> Option<Type>;
fn resolve_type_name(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
fn task(&self) -> Option<&Task>;
fn diagnostics_config(&self) -> DiagnosticsConfig;
fn add_diagnostic(&mut self, diagnostic: Diagnostic);
}
#[derive(Debug)]
pub struct ExprTypeEvaluator<'a, C> {
context: &'a mut C,
placeholders: usize,
}
impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
pub fn new(context: &'a mut C) -> Self {
Self {
context,
placeholders: 0,
}
}
pub fn evaluate_expr<N: TreeNode + Exceptable>(&mut self, expr: &Expr<N>) -> Option<Type> {
match expr {
Expr::Literal(expr) => self.evaluate_literal_expr(expr),
Expr::NameRef(r) => {
let name = r.name();
self.context.resolve_name(name.text(), name.span())
}
Expr::Parenthesized(expr) => self.evaluate_expr(&expr.expr()),
Expr::If(expr) => self.evaluate_if_expr(expr),
Expr::LogicalNot(expr) => self.evaluate_logical_not_expr(expr),
Expr::Negation(expr) => self.evaluate_negation_expr(expr),
Expr::LogicalOr(expr) => self.evaluate_logical_or_expr(expr),
Expr::LogicalAnd(expr) => self.evaluate_logical_and_expr(expr),
Expr::Equality(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(ComparisonOperator::Equality, &lhs, &rhs, expr.span())
}
Expr::Inequality(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(
ComparisonOperator::Inequality,
&lhs,
&rhs,
expr.span(),
)
}
Expr::Less(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(ComparisonOperator::Less, &lhs, &rhs, expr.span())
}
Expr::LessEqual(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(
ComparisonOperator::LessEqual,
&lhs,
&rhs,
expr.span(),
)
}
Expr::Greater(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(ComparisonOperator::Greater, &lhs, &rhs, expr.span())
}
Expr::GreaterEqual(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_comparison_expr(
ComparisonOperator::GreaterEqual,
&lhs,
&rhs,
expr.span(),
)
}
Expr::Addition(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Addition, expr.span(), &lhs, &rhs)
}
Expr::Subtraction(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Subtraction, expr.span(), &lhs, &rhs)
}
Expr::Multiplication(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Multiplication, expr.span(), &lhs, &rhs)
}
Expr::Division(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Division, expr.span(), &lhs, &rhs)
}
Expr::Modulo(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Modulo, expr.span(), &lhs, &rhs)
}
Expr::Exponentiation(expr) => {
let (lhs, rhs) = expr.operands();
self.evaluate_numeric_expr(NumericOperator::Exponentiation, expr.span(), &lhs, &rhs)
}
Expr::Call(expr) => self.evaluate_call_expr(expr),
Expr::Index(expr) => self.evaluate_index_expr(expr),
Expr::Access(expr) => self.evaluate_access_expr(expr),
}
}
fn evaluate_literal_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralExpr<N>,
) -> Option<Type> {
match expr {
LiteralExpr::Boolean(_) => Some(PrimitiveType::Boolean.into()),
LiteralExpr::Integer(_) => Some(PrimitiveType::Integer.into()),
LiteralExpr::Float(_) => Some(PrimitiveType::Float.into()),
LiteralExpr::String(s) => {
for p in s.parts() {
if let StringPart::Placeholder(p) = p {
self.check_placeholder(&p);
}
}
Some(PrimitiveType::String.into())
}
LiteralExpr::Array(expr) => Some(self.evaluate_literal_array(expr)),
LiteralExpr::Pair(expr) => Some(self.evaluate_literal_pair(expr)),
LiteralExpr::Map(expr) => Some(self.evaluate_literal_map(expr)),
LiteralExpr::Object(expr) => Some(self.evaluate_literal_object(expr)),
LiteralExpr::Struct(expr) => self.evaluate_literal_struct(expr),
LiteralExpr::None(_) => Some(Type::None),
LiteralExpr::Hints(expr) => self.evaluate_literal_hints(expr),
LiteralExpr::Input(expr) => self.evaluate_literal_input(expr),
LiteralExpr::Output(expr) => self.evaluate_literal_output(expr),
}
}
pub(crate) fn check_placeholder<N: TreeNode + Exceptable>(
&mut self,
placeholder: &Placeholder<N>,
) {
self.placeholders += 1;
let expr = placeholder.expr();
if let Some(ty) = self.evaluate_expr(&expr) {
if let Some(option) = placeholder.option() {
let valid = match option {
PlaceholderOption::Sep(_) => {
ty == Type::Union
|| ty == Type::None
|| matches!(&ty,
Type::Compound(CompoundType::Array(array_ty), _)
if matches!(array_ty.element_type(), Type::Primitive(_, false) | Type::Union))
}
PlaceholderOption::Default(_) => {
matches!(ty, Type::Primitive(..) | Type::Union | Type::None)
}
PlaceholderOption::TrueFalse(_) => {
matches!(
ty,
Type::Primitive(PrimitiveType::Boolean, _) | Type::Union | Type::None
)
}
};
if !valid {
self.context.add_diagnostic(invalid_placeholder_option(
&ty,
expr.span(),
&option,
));
}
} else {
match ty {
Type::Primitive(..)
| Type::Union
| Type::None
| Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _) => {}
_ => {
self.context
.add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
}
}
}
}
self.placeholders -= 1;
}
fn evaluate_literal_array<N: TreeNode + Exceptable>(&mut self, expr: &LiteralArray<N>) -> Type {
let mut elements = expr.elements();
match elements
.next()
.and_then(|e| Some((self.evaluate_expr(&e)?, e.span())))
{
Some((mut expected, mut expected_span)) => {
for expr in elements {
if let Some(actual) = self.evaluate_expr(&expr) {
match expected.common_type(&actual) {
Some(ty) => {
expected = ty;
expected_span = expr.span();
}
_ => {
self.context.add_diagnostic(no_common_type(
&expected,
expected_span,
&actual,
expr.span(),
));
}
}
}
}
ArrayType::new(expected).into()
}
None => ArrayType::new(Type::Union).into(),
}
}
fn evaluate_literal_pair<N: TreeNode + Exceptable>(&mut self, expr: &LiteralPair<N>) -> Type {
let (left, right) = expr.exprs();
let left = self.evaluate_expr(&left).unwrap_or(Type::Union);
let right = self.evaluate_expr(&right).unwrap_or(Type::Union);
PairType::new(left, right).into()
}
fn evaluate_literal_map<N: TreeNode + Exceptable>(&mut self, expr: &LiteralMap<N>) -> Type {
let map_item_type = |item: LiteralMapItem<N>| {
let (key, value) = item.key_value();
let expected_key = self.evaluate_expr(&key)?;
match expected_key {
Type::Primitive(_, false) | Type::Union => {
}
_ => {
self.context
.add_diagnostic(map_key_not_primitive(key.span(), &expected_key));
return None;
}
}
Some((
expected_key,
key.span(),
self.evaluate_expr(&value)?,
value.span(),
))
};
let mut items = expr.items();
match items.next().and_then(map_item_type) {
Some((
mut expected_key,
mut expected_key_span,
mut expected_value,
mut expected_value_span,
)) => {
for item in items {
let (key, value) = item.key_value();
if let Some(actual_key) = self.evaluate_expr(&key)
&& let Some(actual_value) = self.evaluate_expr(&value)
{
match actual_key {
Type::Primitive(_, false) | Type::Union => {
match expected_key.common_type(&actual_key) {
Some(ty) => {
expected_key = ty;
expected_key_span = key.span();
}
_ => {
self.context.add_diagnostic(no_common_type(
&expected_key,
expected_key_span,
&actual_key,
key.span(),
));
}
}
}
_ => {
self.context
.add_diagnostic(map_key_not_primitive(key.span(), &actual_key));
}
}
match expected_value.common_type(&actual_value) {
Some(ty) => {
expected_value = ty;
expected_value_span = value.span();
}
_ => {
self.context.add_diagnostic(no_common_type(
&expected_value,
expected_value_span,
&actual_value,
value.span(),
));
}
}
}
}
MapType::new(expected_key, expected_value).into()
}
None => MapType::new(Type::Union, Type::Union).into(),
}
}
fn evaluate_literal_object<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralObject<N>,
) -> Type {
for item in expr.items() {
let (_, v) = item.name_value();
self.evaluate_expr(&v);
}
Type::Object
}
fn evaluate_literal_struct<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralStruct<N>,
) -> Option<Type> {
let name = expr.name();
match self.context.resolve_type_name(name.text(), name.span()) {
Ok(ty) => {
let ty = match &ty {
Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), false) => ty,
_ => panic!("type should be a required struct"),
};
let mut present = vec![false; ty.members().len()];
for item in expr.items() {
let (n, v) = item.name_value();
match ty.members().get_full(n.text()) {
Some((index, _, expected)) => {
present[index] = true;
if let Some(actual) = self.evaluate_expr(&v)
&& !actual.is_coercible_to(expected)
{
self.context.add_diagnostic(type_mismatch(
expected,
n.span(),
&actual,
v.span(),
));
}
}
_ => {
self.context
.add_diagnostic(not_a_struct_member(name.text(), &n));
}
}
}
let mut unspecified = present
.iter()
.enumerate()
.filter_map(|(i, present)| {
if *present {
return None;
}
let (name, member_ty) = ty.members().get_index(i).unwrap();
if member_ty.is_optional() {
return None;
}
Some(name.as_str())
})
.peekable();
if unspecified.peek().is_some() {
let mut members = String::new();
let mut count = 0;
while let Some(member) = unspecified.next() {
match (unspecified.peek().is_none(), count) {
(true, c) if c > 1 => members.push_str(", and "),
(true, 1) => members.push_str(" and "),
(false, c) if c > 0 => members.push_str(", "),
_ => {}
}
write!(&mut members, "`{member}`").ok();
count += 1;
}
self.context
.add_diagnostic(missing_struct_members(&name, count, &members));
}
Some(Type::Compound(
CompoundType::Custom(CustomType::Struct(ty.clone())),
false,
))
}
Err(diagnostic) => {
self.context.add_diagnostic(diagnostic);
None
}
}
}
pub(crate) fn evaluate_runtime_item<N: TreeNode + Exceptable>(
&mut self,
name: &Ident<N::Token>,
expr: &Expr<N>,
) {
let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
if !self.evaluate_requirement(name, expr, &expr_ty) {
if let Some(expected) = task_hint_types(self.context.version(), name.text(), false)
&& !expected
.iter()
.any(|target| expr_ty.is_coercible_to(target))
{
self.context.add_diagnostic(multiple_type_mismatch(
expected,
name.span(),
&expr_ty,
expr.span(),
));
}
}
}
pub(crate) fn evaluate_requirements_item<N: TreeNode + Exceptable>(
&mut self,
name: &Ident<N::Token>,
expr: &Expr<N>,
) {
let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
self.evaluate_requirement(name, expr, &expr_ty);
}
fn evaluate_requirement<N: TreeNode>(
&mut self,
name: &Ident<N::Token>,
expr: &Expr<N>,
expr_ty: &Type,
) -> bool {
if let Some(expected) = task_requirement_types(self.context.version(), name.text()) {
if !expected
.iter()
.any(|target| expr_ty.is_coercible_to(target))
{
self.context.add_diagnostic(multiple_type_mismatch(
expected,
name.span(),
expr_ty,
expr.span(),
));
}
return true;
}
false
}
fn evaluate_literal_hints<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralHints<N>,
) -> Option<Type> {
self.context.task()?;
for item in expr.items() {
self.evaluate_hints_item(&item.name(), &item.expr())
}
Some(Type::Hidden(HiddenType::Hints))
}
pub(crate) fn evaluate_hints_item<N: TreeNode + Exceptable>(
&mut self,
name: &Ident<N::Token>,
expr: &Expr<N>,
) {
let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
if let Some(expected) = task_hint_types(self.context.version(), name.text(), true)
&& !expected
.iter()
.any(|target| expr_ty.is_coercible_to(target))
{
self.context.add_diagnostic(multiple_type_mismatch(
expected,
name.span(),
&expr_ty,
expr.span(),
));
}
}
fn evaluate_literal_input<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralInput<N>,
) -> Option<Type> {
self.context.task()?;
for item in expr.items() {
self.evaluate_literal_io_item(item.names(), item.expr(), Io::Input);
}
Some(Type::Hidden(HiddenType::Input))
}
fn evaluate_literal_output<N: TreeNode + Exceptable>(
&mut self,
expr: &LiteralOutput<N>,
) -> Option<Type> {
self.context.task()?;
for item in expr.items() {
self.evaluate_literal_io_item(item.names(), item.expr(), Io::Output);
}
Some(Type::Hidden(HiddenType::Output))
}
fn evaluate_literal_io_item<N: TreeNode + Exceptable>(
&mut self,
names: impl Iterator<Item = Ident<N::Token>>,
expr: Expr<N>,
io: Io,
) {
let mut names = names.enumerate().peekable();
let expr_ty = self.evaluate_expr(&expr).unwrap_or(Type::Union);
let mut span = None;
let mut s: Option<&StructType> = None;
while let Some((i, name)) = names.next() {
let ty = if i == 0 {
span = Some(name.span());
match if io == Io::Input {
self.context
.task()
.expect("should have task")
.inputs()
.get(name.text())
.map(|i| i.ty())
} else {
self.context
.task()
.expect("should have task")
.outputs()
.get(name.text())
.map(|o| o.ty())
} {
Some(ty) => ty,
None => {
self.context.add_diagnostic(unknown_task_io(
self.context.task().expect("should have task").name(),
&name,
io,
));
break;
}
}
} else {
let start = span.unwrap().start();
span = Some(Span::new(start, name.span().end() - start));
let s = s.unwrap();
match s.members().get(name.text()) {
Some(ty) => ty,
None => {
self.context
.add_diagnostic(not_a_struct_member(s.name(), &name));
break;
}
}
};
match ty {
Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => s = Some(ty),
_ if names.peek().is_some() => {
self.context.add_diagnostic(not_a_struct(&name, i == 0));
break;
}
_ => {
}
}
}
if let Some((_, last)) = names.last() {
let start = span.unwrap().start();
span = Some(Span::new(start, last.span().end() - start));
}
if !expr_ty.is_coercible_to(&Type::Hidden(HiddenType::Hints)) {
self.context.add_diagnostic(type_mismatch(
&Type::Hidden(HiddenType::Hints),
span.expect("should have span"),
&expr_ty,
expr.span(),
));
}
}
fn evaluate_if_expr<N: TreeNode + Exceptable>(&mut self, expr: &IfExpr<N>) -> Option<Type> {
let (cond_expr, true_expr, false_expr) = expr.exprs();
let cond_ty = self.evaluate_expr(&cond_expr).unwrap_or(Type::Union);
if !cond_ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(if_conditional_mismatch(&cond_ty, cond_expr.span()));
}
let true_ty = self.evaluate_expr(&true_expr).unwrap_or(Type::Union);
let false_ty = self.evaluate_expr(&false_expr).unwrap_or(Type::Union);
match (true_ty, false_ty) {
(Type::Union, Type::Union) => None,
(Type::Union, false_ty) => Some(false_ty),
(true_ty, Type::Union) => Some(true_ty),
(true_ty, false_ty) => match true_ty.common_type(&false_ty) {
Some(ty) => Some(ty),
_ => {
self.context.add_diagnostic(type_mismatch(
&true_ty,
true_expr.span(),
&false_ty,
false_expr.span(),
));
None
}
},
}
}
fn evaluate_logical_not_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &LogicalNotExpr<N>,
) -> Option<Type> {
let operand = expr.operand();
let ty = self.evaluate_expr(&operand).unwrap_or(Type::Union);
if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(logical_not_mismatch(&ty, operand.span()));
}
Some(PrimitiveType::Boolean.into())
}
fn evaluate_negation_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &NegationExpr<N>,
) -> Option<Type> {
let operand = expr.operand();
let ty = self.evaluate_expr(&operand)?;
if ty.eq(&PrimitiveType::Integer.into()) {
return Some(PrimitiveType::Integer.into());
}
if !ty.is_coercible_to(&PrimitiveType::Float.into()) {
self.context
.add_diagnostic(negation_mismatch(&ty, operand.span()));
return None;
}
Some(PrimitiveType::Float.into())
}
fn evaluate_logical_or_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &LogicalOrExpr<N>,
) -> Option<Type> {
let (lhs, rhs) = expr.operands();
let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(logical_or_mismatch(&ty, lhs.span()));
}
let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(logical_or_mismatch(&ty, rhs.span()));
}
Some(PrimitiveType::Boolean.into())
}
fn evaluate_logical_and_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &LogicalAndExpr<N>,
) -> Option<Type> {
let (lhs, rhs) = expr.operands();
let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(logical_and_mismatch(&ty, lhs.span()));
}
let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
self.context
.add_diagnostic(logical_and_mismatch(&ty, rhs.span()));
}
Some(PrimitiveType::Boolean.into())
}
fn evaluate_comparison_expr<N: TreeNode + Exceptable>(
&mut self,
op: ComparisonOperator,
lhs: &Expr<N>,
rhs: &Expr<N>,
span: Span,
) -> Option<Type> {
let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
if lhs_ty.is_union() || lhs_ty.is_none() || rhs_ty.is_union() || rhs_ty.is_none() {
return Some(PrimitiveType::Boolean.into());
}
for expected in [
Type::from(PrimitiveType::Boolean),
PrimitiveType::Integer.into(),
PrimitiveType::Float.into(),
PrimitiveType::String.into(),
PrimitiveType::File.into(),
PrimitiveType::Directory.into(),
] {
if op != ComparisonOperator::Equality
&& op != ComparisonOperator::Inequality
&& (matches!(
lhs_ty.as_primitive(),
Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
) || matches!(
rhs_ty.as_primitive(),
Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
))
{
continue;
}
if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
return Some(PrimitiveType::Boolean.into());
}
let expected = expected.optional();
if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
return Some(PrimitiveType::Boolean.into());
}
}
if op == ComparisonOperator::Equality || op == ComparisonOperator::Inequality {
if (lhs_ty.is_coercible_to(&Type::Object) && rhs_ty.is_coercible_to(&Type::Object))
|| (lhs_ty.is_coercible_to(&Type::OptionalObject)
&& rhs_ty.is_coercible_to(&Type::OptionalObject))
{
return Some(PrimitiveType::Boolean.into());
}
let equal = match (&lhs_ty, &rhs_ty) {
(
Type::Compound(CompoundType::Array(a), _),
Type::Compound(CompoundType::Array(b), _),
) => a == b,
(
Type::Compound(CompoundType::Pair(a), _),
Type::Compound(CompoundType::Pair(b), _),
) => a == b,
(
Type::Compound(CompoundType::Map(a), _),
Type::Compound(CompoundType::Map(b), _),
) => a == b,
(
Type::Compound(CompoundType::Custom(CustomType::Struct(a)), _),
Type::Compound(CompoundType::Custom(CustomType::Struct(b)), _),
) => a == b,
(
Type::Compound(CompoundType::Custom(CustomType::Enum(a)), _),
Type::Compound(CompoundType::Custom(CustomType::Enum(b)), _),
) => a == b,
_ => false,
};
if equal {
return Some(PrimitiveType::Boolean.into());
}
}
self.context.add_diagnostic(comparison_mismatch(
op,
span,
&lhs_ty,
lhs.span(),
&rhs_ty,
rhs.span(),
));
Some(PrimitiveType::Boolean.into())
}
fn evaluate_numeric_expr<N: TreeNode + Exceptable>(
&mut self,
op: NumericOperator,
span: Span,
lhs: &Expr<N>,
rhs: &Expr<N>,
) -> Option<Type> {
let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
if lhs_ty.eq(&PrimitiveType::Integer.into()) && rhs_ty.eq(&PrimitiveType::Integer.into()) {
return Some(PrimitiveType::Integer.into());
}
if !lhs_ty.is_union()
&& lhs_ty.is_coercible_to(&PrimitiveType::Float.into())
&& !rhs_ty.is_union()
&& rhs_ty.is_coercible_to(&PrimitiveType::Float.into())
{
return Some(PrimitiveType::Float.into());
}
if op == NumericOperator::Addition {
let allow_optional = self.placeholders > 0;
let other = if (!lhs_ty.is_optional() || allow_optional)
&& lhs_ty
.as_primitive()
.map(|p| p == PrimitiveType::String)
.unwrap_or(false)
{
Some((lhs_ty.is_optional(), &rhs_ty, rhs.span()))
} else if (!rhs_ty.is_optional() || allow_optional)
&& rhs_ty
.as_primitive()
.map(|p| p == PrimitiveType::String)
.unwrap_or(false)
{
Some((rhs_ty.is_optional(), &lhs_ty, lhs.span()))
} else {
None
};
if let Some((optional, other, span)) = other {
if (!other.is_optional() || allow_optional)
&& other
.as_primitive()
.map(|p| p != PrimitiveType::Boolean)
.unwrap_or(other.is_union() || (allow_optional && other.is_none()))
{
let ty: Type = PrimitiveType::String.into();
if optional || other.is_optional() {
return Some(ty.optional());
}
return Some(ty);
}
self.context
.add_diagnostic(string_concat_mismatch(other, span));
return None;
}
}
if !lhs_ty.is_union() && !rhs_ty.is_union() {
self.context.add_diagnostic(numeric_mismatch(
op,
span,
&lhs_ty,
lhs.span(),
&rhs_ty,
rhs.span(),
));
}
None
}
fn evaluate_call_expr<N: TreeNode + Exceptable>(&mut self, expr: &CallExpr<N>) -> Option<Type> {
let target = expr.target();
match STDLIB.function(target.text()) {
Some(f) => {
let mut count = 0;
let mut arguments = [const { Type::Union }; MAX_PARAMETERS];
for arg in expr.arguments() {
if count < MAX_PARAMETERS {
arguments[count] = self.evaluate_expr(&arg).unwrap_or(Type::Union);
}
count += 1;
}
match target.text() {
"find" | "matches" | "sub" => {
if let Some(Expr::Literal(LiteralExpr::String(pattern_literal))) =
expr.arguments().nth(1)
&& let Some(value) = pattern_literal.text()
{
let pattern = value.text().to_string();
if let Err(e) = regex::Regex::new(&pattern) {
self.context.add_diagnostic(invalid_regex_pattern(
target.text(),
value.text(),
&e,
pattern_literal.span(),
));
}
}
}
_ => {}
}
let arguments = &arguments[..count.min(MAX_PARAMETERS)];
if count <= MAX_PARAMETERS {
match f.bind(self.context.version(), arguments) {
Ok(binding) => {
if let Some(severity) =
self.context.diagnostics_config().unnecessary_function_call
&& !expr.inner().is_rule_excepted(UNNECESSARY_FUNCTION_CALL)
{
self.check_unnecessary_call(
&target,
arguments,
expr.arguments().map(|e| e.span()),
severity,
);
}
return Some(binding.return_type().clone());
}
Err(FunctionBindError::RequiresVersion(minimum)) => {
self.context.add_diagnostic(unsupported_function(
minimum,
target.text(),
target.span(),
));
}
Err(FunctionBindError::TooFewArguments(minimum)) => {
self.context.add_diagnostic(too_few_arguments(
target.text(),
target.span(),
minimum,
count,
));
}
Err(FunctionBindError::TooManyArguments(maximum)) => {
self.context.add_diagnostic(too_many_arguments(
target.text(),
target.span(),
maximum,
count,
expr.arguments().skip(maximum).map(|e| e.span()),
));
}
Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
self.context.add_diagnostic(argument_type_mismatch(
target.text(),
&expected,
&arguments[index],
expr.arguments()
.nth(index)
.map(|e| e.span())
.expect("should have span"),
));
}
Err(FunctionBindError::Ambiguous { first, second }) => {
self.context.add_diagnostic(ambiguous_argument(
target.text(),
target.span(),
&first,
&second,
));
}
}
} else {
match f.param_min_max(self.context.version()) {
Some((_, max)) => {
assert!(max <= MAX_PARAMETERS);
self.context.add_diagnostic(too_many_arguments(
target.text(),
target.span(),
max,
count,
expr.arguments().skip(max).map(|e| e.span()),
));
}
None => {
self.context.add_diagnostic(unsupported_function(
f.minimum_version(),
target.text(),
target.span(),
));
}
}
}
Some(f.realize_unconstrained_return_type(arguments))
}
None => {
self.context
.add_diagnostic(unknown_function(target.text(), target.span()));
None
}
}
}
fn evaluate_index_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &IndexExpr<N>,
) -> Option<Type> {
let (target, index) = expr.operands();
let target_ty = self.evaluate_expr(&target)?;
let (expected_index_ty, result_ty) = match &target_ty {
Type::Compound(CompoundType::Array(ty), _) => (
Some(PrimitiveType::Integer.into()),
Some(ty.element_type().clone()),
),
Type::Compound(CompoundType::Map(ty), _) => {
(Some(ty.key_type().clone()), Some(ty.value_type().clone()))
}
_ => (None, None),
};
if let Some(expected_index_ty) = expected_index_ty {
let index_ty = self.evaluate_expr(&index).unwrap_or(Type::Union);
if !index_ty.is_coercible_to(&expected_index_ty) {
self.context.add_diagnostic(index_type_mismatch(
&expected_index_ty,
&index_ty,
index.span(),
));
}
}
match result_ty {
Some(ty) => Some(ty),
None => {
self.context
.add_diagnostic(cannot_index(&target_ty, target.span()));
None
}
}
}
fn evaluate_access_expr<N: TreeNode + Exceptable>(
&mut self,
expr: &AccessExpr<N>,
) -> Option<Type> {
let (target, name) = expr.operands();
let ty = self.evaluate_expr(&target)?;
match &ty {
Type::Hidden(HiddenType::TaskPreEvaluation) => {
return match task_member_type_pre_evaluation(name.text()) {
Some(ty) => Some(ty),
None => {
self.context.add_diagnostic(not_a_task_member(&name));
return None;
}
};
}
Type::Hidden(HiddenType::TaskPostEvaluation) => {
return match task_member_type_post_evaluation(self.context.version(), name.text()) {
Some(ty) => Some(ty),
None => {
self.context.add_diagnostic(not_a_task_member(&name));
return None;
}
};
}
Type::Hidden(HiddenType::PreviousTaskData) => {
return match previous_task_data_member_type(name.text()) {
Some(ty) => Some(ty),
None => {
self.context
.add_diagnostic(not_a_previous_task_data_member(&name));
return None;
}
};
}
Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => {
if let Some(ty) = ty.members().get(name.text()) {
return Some(ty.clone());
}
self.context
.add_diagnostic(not_a_struct_member(ty.name(), &name));
return None;
}
Type::Compound(CompoundType::Pair(ty), _) => {
return match name.text() {
"left" => Some(ty.left_type().clone()),
"right" => Some(ty.right_type().clone()),
_ => {
self.context.add_diagnostic(not_a_pair_accessor(&name));
None
}
};
}
Type::Call(ty) => {
if let Some(output) = ty.outputs().get(name.text()) {
return Some(output.ty().clone());
}
self.context
.add_diagnostic(unknown_call_io(ty, &name, Io::Output));
return None;
}
Type::TypeNameRef(custom_ty) => match custom_ty {
CustomType::Struct(_) => {
self.context
.add_diagnostic(cannot_access(&ty, target.span()));
return None;
}
CustomType::Enum(_) => {
return Some(Type::from(CompoundType::Custom(custom_ty.clone())));
}
},
_ => {}
}
if ty.is_coercible_to(&Type::OptionalObject) {
return Some(Type::Union);
}
self.context
.add_diagnostic(cannot_access(&ty, target.span()));
None
}
fn check_unnecessary_call<T: TreeToken>(
&mut self,
target: &Ident<T>,
arguments: &[Type],
mut spans: impl Iterator<Item = Span>,
severity: Severity,
) {
let (label, span, fix) = match target.text() {
"select_first" => {
if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
if ty.is_optional() || ty.is_union() {
return;
}
(
format!("array element {ty:#} is not optional"),
spans.next().expect("should have span"),
"replace the function call with the array's first element",
)
} else {
return;
}
}
"select_all" => {
if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
if ty.is_optional() || ty.is_union() {
return;
}
(
format!("array element {ty:#} is not optional"),
spans.next().expect("should have span"),
"replace the function call with the array itself",
)
} else {
return;
}
}
"defined" => {
if arguments[0].is_optional() || arguments[0].is_union() {
return;
}
(
format!("{ty:#} is not optional", ty = arguments[0]),
spans.next().expect("should have span"),
"replace the function call with `true`",
)
}
_ => return,
};
self.context.add_diagnostic(
unnecessary_function_call(target.text(), target.span(), &label, span)
.with_severity(severity)
.with_fix(fix),
)
}
}