1use std::fmt;
4use std::fmt::Write;
5use std::sync::LazyLock;
6
7use wdl_ast::AstNode;
8use wdl_ast::AstToken;
9use wdl_ast::Diagnostic;
10use wdl_ast::Ident;
11use wdl_ast::Severity;
12use wdl_ast::Span;
13use wdl_ast::SupportedVersion;
14use wdl_ast::TreeNode;
15use wdl_ast::v1;
16use wdl_ast::v1::AccessExpr;
17use wdl_ast::v1::CallExpr;
18use wdl_ast::v1::Expr;
19use wdl_ast::v1::IfExpr;
20use wdl_ast::v1::IndexExpr;
21use wdl_ast::v1::LiteralArray;
22use wdl_ast::v1::LiteralExpr;
23use wdl_ast::v1::LiteralHints;
24use wdl_ast::v1::LiteralInput;
25use wdl_ast::v1::LiteralMap;
26use wdl_ast::v1::LiteralMapItem;
27use wdl_ast::v1::LiteralObject;
28use wdl_ast::v1::LiteralOutput;
29use wdl_ast::v1::LiteralPair;
30use wdl_ast::v1::LiteralStruct;
31use wdl_ast::v1::LogicalAndExpr;
32use wdl_ast::v1::LogicalNotExpr;
33use wdl_ast::v1::LogicalOrExpr;
34use wdl_ast::v1::NegationExpr;
35use wdl_ast::v1::Placeholder;
36use wdl_ast::v1::PlaceholderOption;
37use wdl_ast::v1::StringPart;
38use wdl_ast::v1::TASK_FIELD_ATTEMPT;
39use wdl_ast::v1::TASK_FIELD_CONTAINER;
40use wdl_ast::v1::TASK_FIELD_CPU;
41use wdl_ast::v1::TASK_FIELD_DISKS;
42use wdl_ast::v1::TASK_FIELD_END_TIME;
43use wdl_ast::v1::TASK_FIELD_EXT;
44use wdl_ast::v1::TASK_FIELD_FPGA;
45use wdl_ast::v1::TASK_FIELD_GPU;
46use wdl_ast::v1::TASK_FIELD_ID;
47use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
48use wdl_ast::v1::TASK_FIELD_MEMORY;
49use wdl_ast::v1::TASK_FIELD_META;
50use wdl_ast::v1::TASK_FIELD_NAME;
51use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
52use wdl_ast::v1::TASK_FIELD_PREVIOUS;
53use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
54use wdl_ast::v1::TASK_HINT_CACHEABLE;
55use wdl_ast::v1::TASK_HINT_DISKS;
56use wdl_ast::v1::TASK_HINT_FPGA;
57use wdl_ast::v1::TASK_HINT_GPU;
58use wdl_ast::v1::TASK_HINT_INPUTS;
59use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL;
60use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS;
61use wdl_ast::v1::TASK_HINT_MAX_CPU;
62use wdl_ast::v1::TASK_HINT_MAX_CPU_ALIAS;
63use wdl_ast::v1::TASK_HINT_MAX_MEMORY;
64use wdl_ast::v1::TASK_HINT_MAX_MEMORY_ALIAS;
65use wdl_ast::v1::TASK_HINT_OUTPUTS;
66use wdl_ast::v1::TASK_HINT_SHORT_TASK;
67use wdl_ast::v1::TASK_HINT_SHORT_TASK_ALIAS;
68use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER;
69use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER_ALIAS;
70use wdl_ast::v1::TASK_REQUIREMENT_CPU;
71use wdl_ast::v1::TASK_REQUIREMENT_DISKS;
72use wdl_ast::v1::TASK_REQUIREMENT_FPGA;
73use wdl_ast::v1::TASK_REQUIREMENT_GPU;
74use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES;
75use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES_ALIAS;
76use wdl_ast::v1::TASK_REQUIREMENT_MEMORY;
77use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES;
78use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES_ALIAS;
79use wdl_ast::version::V1;
80use wdl_grammar::SyntaxKind;
81
82use super::ArrayType;
83use super::CompoundType;
84use super::HiddenType;
85use super::MapType;
86use super::Optional;
87use super::PairType;
88use super::PrimitiveType;
89use super::StructType;
90use super::Type;
91use super::TypeNameResolver;
92use crate::Exceptable;
93use crate::UnnecessaryFunctionCall;
94use crate::config::DiagnosticsConfig;
95use crate::diagnostics::Io;
96use crate::diagnostics::ambiguous_argument;
97use crate::diagnostics::argument_type_mismatch;
98use crate::diagnostics::cannot_access;
99use crate::diagnostics::cannot_coerce_to_string;
100use crate::diagnostics::cannot_index;
101use crate::diagnostics::comparison_mismatch;
102use crate::diagnostics::if_conditional_mismatch;
103use crate::diagnostics::index_type_mismatch;
104use crate::diagnostics::invalid_placeholder_option;
105use crate::diagnostics::invalid_regex_pattern;
106use crate::diagnostics::logical_and_mismatch;
107use crate::diagnostics::logical_not_mismatch;
108use crate::diagnostics::logical_or_mismatch;
109use crate::diagnostics::map_key_not_primitive;
110use crate::diagnostics::missing_struct_members;
111use crate::diagnostics::multiple_type_mismatch;
112use crate::diagnostics::negation_mismatch;
113use crate::diagnostics::no_common_type;
114use crate::diagnostics::not_a_pair_accessor;
115use crate::diagnostics::not_a_previous_task_data_member;
116use crate::diagnostics::not_a_struct;
117use crate::diagnostics::not_a_struct_member;
118use crate::diagnostics::not_a_task_member;
119use crate::diagnostics::numeric_mismatch;
120use crate::diagnostics::string_concat_mismatch;
121use crate::diagnostics::too_few_arguments;
122use crate::diagnostics::too_many_arguments;
123use crate::diagnostics::type_mismatch;
124use crate::diagnostics::unknown_call_io;
125use crate::diagnostics::unknown_function;
126use crate::diagnostics::unknown_task_io;
127use crate::diagnostics::unnecessary_function_call;
128use crate::diagnostics::unsupported_function;
129use crate::document::Task;
130use crate::stdlib::FunctionBindError;
131use crate::stdlib::MAX_PARAMETERS;
132use crate::stdlib::STDLIB;
133use crate::types::Coercible;
134use crate::types::CustomType;
135
136pub fn task_member_type_pre_evaluation(name: &str) -> Option<Type> {
143 match name {
144 TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
145 TASK_FIELD_ATTEMPT => Some(PrimitiveType::Integer.into()),
146 TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
147 TASK_FIELD_PREVIOUS => Some(Type::Hidden(HiddenType::PreviousTaskData)),
148 _ => None,
149 }
150}
151
152pub fn task_member_type_post_evaluation(version: SupportedVersion, name: &str) -> Option<Type> {
159 match name {
160 TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
161 TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
162 TASK_FIELD_CPU => Some(PrimitiveType::Float.into()),
163 TASK_FIELD_MEMORY | TASK_FIELD_ATTEMPT | TASK_FIELD_RETURN_CODE => {
164 Some(PrimitiveType::Integer.into())
165 }
166 TASK_FIELD_GPU | TASK_FIELD_FPGA => Some(STDLIB.array_string_type().clone().into()),
167 TASK_FIELD_DISKS => Some(STDLIB.map_string_int_type().clone().into()),
168 TASK_FIELD_END_TIME => Some(Type::from(PrimitiveType::Integer).optional()),
169 TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
170 TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
171 Some(PrimitiveType::Integer.into())
172 }
173 TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
174 Some(Type::Hidden(HiddenType::PreviousTaskData))
175 }
176 _ => None,
177 }
178}
179
180pub fn previous_task_data_member_type(name: &str) -> Option<Type> {
184 match name {
185 TASK_FIELD_MEMORY => Some(Type::from(PrimitiveType::Integer).optional()),
186 TASK_FIELD_CPU => Some(Type::from(PrimitiveType::Float).optional()),
187 TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
188 TASK_FIELD_GPU | TASK_FIELD_FPGA => {
189 Some(Type::from(STDLIB.array_string_type().clone()).optional())
190 }
191 TASK_FIELD_DISKS => Some(Type::from(STDLIB.map_string_int_type().clone()).optional()),
192 TASK_FIELD_MAX_RETRIES => Some(Type::from(PrimitiveType::Integer).optional()),
193 _ => None,
194 }
195}
196
197pub fn task_requirement_types(version: SupportedVersion, name: &str) -> Option<&'static [Type]> {
204 static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
206 Box::new([
207 PrimitiveType::String.into(),
208 STDLIB.array_string_type().clone().into(),
209 ])
210 });
211 const CPU_TYPES: &[Type] = &[
213 Type::Primitive(PrimitiveType::Integer, false),
214 Type::Primitive(PrimitiveType::Float, false),
215 ];
216 const MEMORY_TYPES: &[Type] = &[
218 Type::Primitive(PrimitiveType::Integer, false),
219 Type::Primitive(PrimitiveType::String, false),
220 ];
221 const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
223 const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
225 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
227 Box::new([
228 PrimitiveType::Integer.into(),
229 PrimitiveType::String.into(),
230 STDLIB.array_string_type().clone().into(),
231 ])
232 });
233 const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
235 static RETURN_CODES_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
237 Box::new([
238 PrimitiveType::Integer.into(),
239 PrimitiveType::String.into(),
240 STDLIB.array_int_type().clone().into(),
241 ])
242 });
243
244 match name {
245 TASK_REQUIREMENT_CONTAINER | TASK_REQUIREMENT_CONTAINER_ALIAS => Some(&CONTAINER_TYPES),
246 TASK_REQUIREMENT_CPU => Some(CPU_TYPES),
247 TASK_REQUIREMENT_DISKS => Some(&DISKS_TYPES),
248 TASK_REQUIREMENT_GPU => Some(GPU_TYPES),
249 TASK_REQUIREMENT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
250 TASK_REQUIREMENT_MAX_RETRIES if version >= SupportedVersion::V1(V1::Two) => {
251 Some(MAX_RETRIES_TYPES)
252 }
253 TASK_REQUIREMENT_MAX_RETRIES_ALIAS => Some(MAX_RETRIES_TYPES),
254 TASK_REQUIREMENT_MEMORY => Some(MEMORY_TYPES),
255 TASK_REQUIREMENT_RETURN_CODES if version >= SupportedVersion::V1(V1::Two) => {
256 Some(&RETURN_CODES_TYPES)
257 }
258 TASK_REQUIREMENT_RETURN_CODES_ALIAS => Some(&RETURN_CODES_TYPES),
259 _ => None,
260 }
261}
262
263pub fn task_hint_types(
267 version: SupportedVersion,
268 name: &str,
269 use_hidden_types: bool,
270) -> Option<&'static [Type]> {
271 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
273 Box::new([
274 PrimitiveType::String.into(),
275 STDLIB.map_string_string_type().clone().into(),
276 ])
277 });
278 const FPGA_TYPES: &[Type] = &[
280 Type::Primitive(PrimitiveType::Integer, false),
281 Type::Primitive(PrimitiveType::String, false),
282 ];
283 const GPU_TYPES: &[Type] = &[
285 Type::Primitive(PrimitiveType::Integer, false),
286 Type::Primitive(PrimitiveType::String, false),
287 ];
288 const INPUTS_TYPES: &[Type] = &[Type::Object];
290 const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Input)];
292 const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
294 const MAX_CPU_TYPES: &[Type] = &[
296 Type::Primitive(PrimitiveType::Integer, false),
297 Type::Primitive(PrimitiveType::Float, false),
298 ];
299 const MAX_MEMORY_TYPES: &[Type] = &[
301 Type::Primitive(PrimitiveType::Integer, false),
302 Type::Primitive(PrimitiveType::String, false),
303 ];
304 const OUTPUTS_TYPES: &[Type] = &[Type::Object];
306 const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Output)];
308 const SHORT_TASK_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
310 const CACHEABLE_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
312
313 match name {
314 TASK_HINT_DISKS => Some(&DISKS_TYPES),
315 TASK_HINT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
316 TASK_HINT_GPU => Some(GPU_TYPES),
317 TASK_HINT_INPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
318 Some(INPUTS_HIDDEN_TYPES)
319 }
320 TASK_HINT_INPUTS => Some(INPUTS_TYPES),
321 TASK_HINT_LOCALIZATION_OPTIONAL if version >= SupportedVersion::V1(V1::Two) => {
322 Some(LOCALIZATION_OPTIONAL_TYPES)
323 }
324 TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS => Some(LOCALIZATION_OPTIONAL_TYPES),
325 TASK_HINT_MAX_CPU if version >= SupportedVersion::V1(V1::Two) => Some(MAX_CPU_TYPES),
326 TASK_HINT_MAX_CPU_ALIAS => Some(MAX_CPU_TYPES),
327 TASK_HINT_MAX_MEMORY if version >= SupportedVersion::V1(V1::Two) => Some(MAX_MEMORY_TYPES),
328 TASK_HINT_MAX_MEMORY_ALIAS => Some(MAX_MEMORY_TYPES),
329 TASK_HINT_OUTPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
330 Some(OUTPUTS_HIDDEN_TYPES)
331 }
332 TASK_HINT_OUTPUTS => Some(OUTPUTS_TYPES),
333 TASK_HINT_SHORT_TASK if version >= SupportedVersion::V1(V1::Two) => Some(SHORT_TASK_TYPES),
334 TASK_HINT_SHORT_TASK_ALIAS => Some(SHORT_TASK_TYPES),
335 TASK_HINT_CACHEABLE => Some(CACHEABLE_TYPES),
336 _ => None,
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum ComparisonOperator {
343 Equality,
345 Inequality,
347 Less,
349 LessEqual,
351 Greater,
353 GreaterEqual,
355}
356
357impl fmt::Display for ComparisonOperator {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 write!(
360 f,
361 "{}",
362 match self {
363 Self::Equality => "==",
364 Self::Inequality => "!=",
365 Self::Less => "<",
366 Self::LessEqual => "<=",
367 Self::Greater => ">",
368 Self::GreaterEqual => ">=",
369 }
370 )
371 }
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum NumericOperator {
377 Addition,
379 Subtraction,
381 Multiplication,
383 Division,
385 Modulo,
387 Exponentiation,
389}
390
391impl fmt::Display for NumericOperator {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 write!(
394 f,
395 "{}",
396 match self {
397 Self::Addition => "addition",
398 Self::Subtraction => "subtraction",
399 Self::Multiplication => "multiplication",
400 Self::Division => "division",
401 Self::Modulo => "remainder",
402 Self::Exponentiation => "exponentiation",
403 }
404 )
405 }
406}
407
408#[derive(Debug)]
410pub struct AstTypeConverter<R>(R);
411
412impl<R> AstTypeConverter<R>
413where
414 R: TypeNameResolver,
415{
416 pub fn new(resolver: R) -> Self {
418 Self(resolver)
419 }
420
421 pub fn convert_type<N: TreeNode>(&mut self, ty: &v1::Type<N>) -> Result<Type, Diagnostic> {
426 let optional = ty.is_optional();
427
428 let ty: Type = match ty {
429 v1::Type::Map(ty) => {
430 let ty = self.convert_map_type(ty)?;
431 ty.into()
432 }
433 v1::Type::Array(ty) => {
434 let ty = self.convert_array_type(ty)?;
435 ty.into()
436 }
437 v1::Type::Pair(ty) => {
438 let ty = self.convert_pair_type(ty)?;
439 ty.into()
440 }
441 v1::Type::Object(_) => Type::Object,
442 v1::Type::Ref(r) => {
443 let name = r.name();
444 self.0.resolve(name.text(), name.span())?
445 }
446 v1::Type::Primitive(ty) => Type::Primitive(ty.kind().into(), false),
447 };
448
449 if optional { Ok(ty.optional()) } else { Ok(ty) }
450 }
451
452 pub fn convert_array_type<N: TreeNode>(
457 &mut self,
458 ty: &v1::ArrayType<N>,
459 ) -> Result<ArrayType, Diagnostic> {
460 let element_type = self.convert_type(&ty.element_type())?;
461 if ty.is_non_empty() {
462 Ok(ArrayType::non_empty(element_type))
463 } else {
464 Ok(ArrayType::new(element_type))
465 }
466 }
467
468 pub fn convert_pair_type<N: TreeNode>(
473 &mut self,
474 ty: &v1::PairType<N>,
475 ) -> Result<PairType, Diagnostic> {
476 let (left_type, right_type) = ty.types();
477 Ok(PairType::new(
478 self.convert_type(&left_type)?,
479 self.convert_type(&right_type)?,
480 ))
481 }
482
483 pub fn convert_map_type<N: TreeNode>(
488 &mut self,
489 ty: &v1::MapType<N>,
490 ) -> Result<MapType, Diagnostic> {
491 let (key_type, value_type) = ty.types();
492 let key_type =
493 Type::Primitive(PrimitiveType::from(key_type.kind()), key_type.is_optional());
494
495 if key_type.is_optional() {
497 return Err(map_key_not_primitive(ty.types().0.span(), &key_type));
498 }
499
500 Ok(MapType::new(key_type, self.convert_type(&value_type)?))
501 }
502
503 pub fn convert_struct_type<N: TreeNode>(
508 &mut self,
509 definition: &v1::StructDefinition<N>,
510 ) -> Result<StructType, Diagnostic> {
511 Ok(StructType::new(
512 definition.name().text().to_string(),
513 definition
514 .members()
515 .map(|d| Ok((d.name().text().to_string(), self.convert_type(&d.ty())?)))
516 .collect::<Result<Vec<_>, Diagnostic>>()?,
517 ))
518 }
519}
520
521impl From<v1::PrimitiveTypeKind> for PrimitiveType {
522 fn from(value: v1::PrimitiveTypeKind) -> Self {
523 match value {
524 v1::PrimitiveTypeKind::Boolean => Self::Boolean,
525 v1::PrimitiveTypeKind::Integer => Self::Integer,
526 v1::PrimitiveTypeKind::Float => Self::Float,
527 v1::PrimitiveTypeKind::String => Self::String,
528 v1::PrimitiveTypeKind::File => Self::File,
529 v1::PrimitiveTypeKind::Directory => Self::Directory,
530 }
531 }
532}
533
534pub trait EvaluationContext {
536 fn version(&self) -> SupportedVersion;
538
539 fn resolve_name(&mut self, name: &str, span: Span) -> Option<Type>;
548
549 fn resolve_type_name(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
554
555 fn task(&self) -> Option<&Task>;
559
560 fn diagnostics_config(&self) -> DiagnosticsConfig;
562
563 fn add_diagnostic(&mut self, diagnostic: Diagnostic);
565
566 fn exceptable_add_diagnostic<N: TreeNode + Exceptable>(
569 &mut self,
570 diagnostic: Diagnostic,
571 element: &N,
572 exceptable_nodes: &Option<&'static [SyntaxKind]>,
573 );
574}
575
576#[derive(Debug)]
578pub struct ExprTypeEvaluator<'a, C> {
579 context: &'a mut C,
581 placeholders: usize,
589}
590
591impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
592 pub fn new(context: &'a mut C) -> Self {
594 Self {
595 context,
596 placeholders: 0,
597 }
598 }
599
600 pub fn evaluate_expr<N: TreeNode + Exceptable>(&mut self, expr: &Expr<N>) -> Option<Type> {
604 match expr {
605 Expr::Literal(expr) => self.evaluate_literal_expr(expr),
606 Expr::NameRef(r) => {
607 let name = r.name();
608 self.context.resolve_name(name.text(), name.span())
609 }
610 Expr::Parenthesized(expr) => self.evaluate_expr(&expr.expr()),
611 Expr::If(expr) => self.evaluate_if_expr(expr),
612 Expr::LogicalNot(expr) => self.evaluate_logical_not_expr(expr),
613 Expr::Negation(expr) => self.evaluate_negation_expr(expr),
614 Expr::LogicalOr(expr) => self.evaluate_logical_or_expr(expr),
615 Expr::LogicalAnd(expr) => self.evaluate_logical_and_expr(expr),
616 Expr::Equality(expr) => {
617 let (lhs, rhs) = expr.operands();
618 self.evaluate_comparison_expr(ComparisonOperator::Equality, &lhs, &rhs, expr.span())
619 }
620 Expr::Inequality(expr) => {
621 let (lhs, rhs) = expr.operands();
622 self.evaluate_comparison_expr(
623 ComparisonOperator::Inequality,
624 &lhs,
625 &rhs,
626 expr.span(),
627 )
628 }
629 Expr::Less(expr) => {
630 let (lhs, rhs) = expr.operands();
631 self.evaluate_comparison_expr(ComparisonOperator::Less, &lhs, &rhs, expr.span())
632 }
633 Expr::LessEqual(expr) => {
634 let (lhs, rhs) = expr.operands();
635 self.evaluate_comparison_expr(
636 ComparisonOperator::LessEqual,
637 &lhs,
638 &rhs,
639 expr.span(),
640 )
641 }
642 Expr::Greater(expr) => {
643 let (lhs, rhs) = expr.operands();
644 self.evaluate_comparison_expr(ComparisonOperator::Greater, &lhs, &rhs, expr.span())
645 }
646 Expr::GreaterEqual(expr) => {
647 let (lhs, rhs) = expr.operands();
648 self.evaluate_comparison_expr(
649 ComparisonOperator::GreaterEqual,
650 &lhs,
651 &rhs,
652 expr.span(),
653 )
654 }
655 Expr::Addition(expr) => {
656 let (lhs, rhs) = expr.operands();
657 self.evaluate_numeric_expr(NumericOperator::Addition, expr.span(), &lhs, &rhs)
658 }
659 Expr::Subtraction(expr) => {
660 let (lhs, rhs) = expr.operands();
661 self.evaluate_numeric_expr(NumericOperator::Subtraction, expr.span(), &lhs, &rhs)
662 }
663 Expr::Multiplication(expr) => {
664 let (lhs, rhs) = expr.operands();
665 self.evaluate_numeric_expr(NumericOperator::Multiplication, expr.span(), &lhs, &rhs)
666 }
667 Expr::Division(expr) => {
668 let (lhs, rhs) = expr.operands();
669 self.evaluate_numeric_expr(NumericOperator::Division, expr.span(), &lhs, &rhs)
670 }
671 Expr::Modulo(expr) => {
672 let (lhs, rhs) = expr.operands();
673 self.evaluate_numeric_expr(NumericOperator::Modulo, expr.span(), &lhs, &rhs)
674 }
675 Expr::Exponentiation(expr) => {
676 let (lhs, rhs) = expr.operands();
677 self.evaluate_numeric_expr(NumericOperator::Exponentiation, expr.span(), &lhs, &rhs)
678 }
679 Expr::Call(expr) => self.evaluate_call_expr(expr),
680 Expr::Index(expr) => self.evaluate_index_expr(expr),
681 Expr::Access(expr) => self.evaluate_access_expr(expr),
682 }
683 }
684
685 fn evaluate_literal_expr<N: TreeNode + Exceptable>(
687 &mut self,
688 expr: &LiteralExpr<N>,
689 ) -> Option<Type> {
690 match expr {
691 LiteralExpr::Boolean(_) => Some(PrimitiveType::Boolean.into()),
692 LiteralExpr::Integer(_) => Some(PrimitiveType::Integer.into()),
693 LiteralExpr::Float(_) => Some(PrimitiveType::Float.into()),
694 LiteralExpr::String(s) => {
695 for p in s.parts() {
696 if let StringPart::Placeholder(p) = p {
697 self.check_placeholder(&p);
698 }
699 }
700
701 Some(PrimitiveType::String.into())
702 }
703 LiteralExpr::Array(expr) => Some(self.evaluate_literal_array(expr)),
704 LiteralExpr::Pair(expr) => Some(self.evaluate_literal_pair(expr)),
705 LiteralExpr::Map(expr) => Some(self.evaluate_literal_map(expr)),
706 LiteralExpr::Object(expr) => Some(self.evaluate_literal_object(expr)),
707 LiteralExpr::Struct(expr) => self.evaluate_literal_struct(expr),
708 LiteralExpr::None(_) => Some(Type::None),
709 LiteralExpr::Hints(expr) => self.evaluate_literal_hints(expr),
710 LiteralExpr::Input(expr) => self.evaluate_literal_input(expr),
711 LiteralExpr::Output(expr) => self.evaluate_literal_output(expr),
712 }
713 }
714
715 pub(crate) fn check_placeholder<N: TreeNode + Exceptable>(
717 &mut self,
718 placeholder: &Placeholder<N>,
719 ) {
720 self.placeholders += 1;
721
722 let expr = placeholder.expr();
725 if let Some(ty) = self.evaluate_expr(&expr) {
726 if let Some(option) = placeholder.option() {
727 let valid = match option {
728 PlaceholderOption::Sep(_) => {
729 ty == Type::Union
730 || ty == Type::None
731 || matches!(&ty,
732 Type::Compound(CompoundType::Array(array_ty), _)
733 if matches!(array_ty.element_type(), Type::Primitive(_, false) | Type::Union))
734 }
735 PlaceholderOption::Default(_) => {
736 matches!(ty, Type::Primitive(..) | Type::Union | Type::None)
737 }
738 PlaceholderOption::TrueFalse(_) => {
739 matches!(
740 ty,
741 Type::Primitive(PrimitiveType::Boolean, _) | Type::Union | Type::None
742 )
743 }
744 };
745
746 if !valid {
747 self.context.add_diagnostic(invalid_placeholder_option(
748 &ty,
749 expr.span(),
750 &option,
751 ));
752 }
753 } else {
754 match ty {
755 Type::Primitive(..)
756 | Type::Union
757 | Type::None
758 | Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _) => {}
759 _ => {
760 self.context
761 .add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
762 }
763 }
764 }
765 }
766
767 self.placeholders -= 1;
768 }
769
770 fn evaluate_literal_array<N: TreeNode + Exceptable>(&mut self, expr: &LiteralArray<N>) -> Type {
772 let mut elements = expr.elements();
775 match elements
776 .next()
777 .and_then(|e| Some((self.evaluate_expr(&e)?, e.span())))
778 {
779 Some((mut expected, mut expected_span)) => {
780 for expr in elements {
782 if let Some(actual) = self.evaluate_expr(&expr) {
783 match expected.common_type(&actual) {
784 Some(ty) => {
785 expected = ty;
786 expected_span = expr.span();
787 }
788 _ => {
789 self.context.add_diagnostic(no_common_type(
790 &expected,
791 expected_span,
792 &actual,
793 expr.span(),
794 ));
795 }
796 }
797 }
798 }
799
800 ArrayType::new(expected).into()
801 }
802 None => ArrayType::new(Type::Union).into(),
804 }
805 }
806
807 fn evaluate_literal_pair<N: TreeNode + Exceptable>(&mut self, expr: &LiteralPair<N>) -> Type {
809 let (left, right) = expr.exprs();
810 let left = self.evaluate_expr(&left).unwrap_or(Type::Union);
811 let right = self.evaluate_expr(&right).unwrap_or(Type::Union);
812 PairType::new(left, right).into()
813 }
814
815 fn evaluate_literal_map<N: TreeNode + Exceptable>(&mut self, expr: &LiteralMap<N>) -> Type {
817 let map_item_type = |item: LiteralMapItem<N>| {
818 let (key, value) = item.key_value();
819 let expected_key = self.evaluate_expr(&key)?;
820 match expected_key {
821 Type::Primitive(_, false) | Type::Union => {
822 }
824 _ => {
825 self.context
826 .add_diagnostic(map_key_not_primitive(key.span(), &expected_key));
827 return None;
828 }
829 }
830
831 Some((
832 expected_key,
833 key.span(),
834 self.evaluate_expr(&value)?,
835 value.span(),
836 ))
837 };
838
839 let mut items = expr.items();
840 match items.next().and_then(map_item_type) {
841 Some((
842 mut expected_key,
843 mut expected_key_span,
844 mut expected_value,
845 mut expected_value_span,
846 )) => {
847 for item in items {
849 let (key, value) = item.key_value();
850 if let Some(actual_key) = self.evaluate_expr(&key)
851 && let Some(actual_value) = self.evaluate_expr(&value)
852 {
853 match actual_key {
855 Type::Primitive(_, false) | Type::Union => {
856 match expected_key.common_type(&actual_key) {
857 Some(ty) => {
858 expected_key = ty;
859 expected_key_span = key.span();
860 }
861 _ => {
862 self.context.add_diagnostic(no_common_type(
863 &expected_key,
864 expected_key_span,
865 &actual_key,
866 key.span(),
867 ));
868 }
869 }
870 }
871 _ => {
872 self.context
873 .add_diagnostic(map_key_not_primitive(key.span(), &actual_key));
874 }
875 }
876
877 match expected_value.common_type(&actual_value) {
878 Some(ty) => {
879 expected_value = ty;
880 expected_value_span = value.span();
881 }
882 _ => {
883 self.context.add_diagnostic(no_common_type(
884 &expected_value,
885 expected_value_span,
886 &actual_value,
887 value.span(),
888 ));
889 }
890 }
891 }
892 }
893
894 MapType::new(expected_key, expected_value).into()
895 }
896 None => MapType::new(Type::Union, Type::Union).into(),
898 }
899 }
900
901 fn evaluate_literal_object<N: TreeNode + Exceptable>(
903 &mut self,
904 expr: &LiteralObject<N>,
905 ) -> Type {
906 for item in expr.items() {
908 let (_, v) = item.name_value();
909 self.evaluate_expr(&v);
910 }
911
912 Type::Object
913 }
914
915 fn evaluate_literal_struct<N: TreeNode + Exceptable>(
917 &mut self,
918 expr: &LiteralStruct<N>,
919 ) -> Option<Type> {
920 let name = expr.name();
921 match self.context.resolve_type_name(name.text(), name.span()) {
922 Ok(ty) => {
923 let ty = match &ty {
924 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), false) => ty,
925 _ => panic!("type should be a required struct"),
926 };
927
928 let mut present = vec![false; ty.members().len()];
930
931 for item in expr.items() {
933 let (n, v) = item.name_value();
934 match ty.members().get_full(n.text()) {
935 Some((index, _, expected)) => {
936 present[index] = true;
937 if let Some(actual) = self.evaluate_expr(&v)
938 && !actual.is_coercible_to(expected)
939 {
940 self.context.add_diagnostic(type_mismatch(
941 expected,
942 n.span(),
943 &actual,
944 v.span(),
945 ));
946 }
947 }
948 _ => {
949 self.context
951 .add_diagnostic(not_a_struct_member(name.text(), &n));
952 }
953 }
954 }
955
956 let mut unspecified = present
958 .iter()
959 .enumerate()
960 .filter_map(|(i, present)| {
961 if *present {
962 return None;
963 }
964
965 let (name, member_ty) = ty.members().get_index(i).unwrap();
966 if member_ty.is_optional() {
967 return None;
968 }
969
970 Some(name.as_str())
971 })
972 .peekable();
973
974 if unspecified.peek().is_some() {
975 let mut members = String::new();
976 let mut count = 0;
977 while let Some(member) = unspecified.next() {
978 match (unspecified.peek().is_none(), count) {
979 (true, c) if c > 1 => members.push_str(", and "),
980 (true, 1) => members.push_str(" and "),
981 (false, c) if c > 0 => members.push_str(", "),
982 _ => {}
983 }
984
985 write!(&mut members, "`{member}`").ok();
986 count += 1;
987 }
988
989 self.context
990 .add_diagnostic(missing_struct_members(&name, count, &members));
991 }
992
993 Some(Type::Compound(
994 CompoundType::Custom(CustomType::Struct(ty.clone())),
995 false,
996 ))
997 }
998 Err(diagnostic) => {
999 self.context.add_diagnostic(diagnostic);
1000 None
1001 }
1002 }
1003 }
1004
1005 pub(crate) fn evaluate_runtime_item<N: TreeNode + Exceptable>(
1007 &mut self,
1008 name: &Ident<N::Token>,
1009 expr: &Expr<N>,
1010 ) {
1011 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1012
1013 if self.context.version() < SupportedVersion::V1(V1::One)
1024 && matches!(
1025 name.text(),
1026 TASK_REQUIREMENT_CPU
1027 | TASK_REQUIREMENT_GPU
1028 | TASK_REQUIREMENT_DISKS
1029 | TASK_REQUIREMENT_MAX_RETRIES_ALIAS
1030 | TASK_REQUIREMENT_RETURN_CODES_ALIAS
1031 )
1032 {
1033 return;
1034 }
1035
1036 if !self.evaluate_requirement(name, expr, &expr_ty) {
1037 if let Some(expected) = task_hint_types(self.context.version(), name.text(), false)
1040 && !expected
1041 .iter()
1042 .any(|target| expr_ty.is_coercible_to(target))
1043 {
1044 self.context.add_diagnostic(multiple_type_mismatch(
1045 expected,
1046 name.span(),
1047 &expr_ty,
1048 expr.span(),
1049 ));
1050 }
1051 }
1052 }
1053
1054 pub(crate) fn evaluate_requirements_item<N: TreeNode + Exceptable>(
1056 &mut self,
1057 name: &Ident<N::Token>,
1058 expr: &Expr<N>,
1059 ) {
1060 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1061 self.evaluate_requirement(name, expr, &expr_ty);
1062 }
1063
1064 fn evaluate_requirement<N: TreeNode>(
1070 &mut self,
1071 name: &Ident<N::Token>,
1072 expr: &Expr<N>,
1073 expr_ty: &Type,
1074 ) -> bool {
1075 if let Some(expected) = task_requirement_types(self.context.version(), name.text()) {
1076 if !expected
1077 .iter()
1078 .any(|target| expr_ty.is_coercible_to(target))
1079 {
1080 self.context.add_diagnostic(multiple_type_mismatch(
1081 expected,
1082 name.span(),
1083 expr_ty,
1084 expr.span(),
1085 ));
1086 }
1087
1088 return true;
1089 }
1090
1091 false
1092 }
1093
1094 fn evaluate_literal_hints<N: TreeNode + Exceptable>(
1096 &mut self,
1097 expr: &LiteralHints<N>,
1098 ) -> Option<Type> {
1099 self.context.task()?;
1100
1101 for item in expr.items() {
1102 self.evaluate_hints_item(&item.name(), &item.expr())
1103 }
1104
1105 Some(Type::Hidden(HiddenType::Hints))
1106 }
1107
1108 pub(crate) fn evaluate_hints_item<N: TreeNode + Exceptable>(
1111 &mut self,
1112 name: &Ident<N::Token>,
1113 expr: &Expr<N>,
1114 ) {
1115 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1116 if let Some(expected) = task_hint_types(self.context.version(), name.text(), true)
1117 && !expected
1118 .iter()
1119 .any(|target| expr_ty.is_coercible_to(target))
1120 {
1121 self.context.add_diagnostic(multiple_type_mismatch(
1122 expected,
1123 name.span(),
1124 &expr_ty,
1125 expr.span(),
1126 ));
1127 }
1128 }
1129
1130 fn evaluate_literal_input<N: TreeNode + Exceptable>(
1132 &mut self,
1133 expr: &LiteralInput<N>,
1134 ) -> Option<Type> {
1135 self.context.task()?;
1137
1138 for item in expr.items() {
1140 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Input);
1141 }
1142
1143 Some(Type::Hidden(HiddenType::Input))
1144 }
1145
1146 fn evaluate_literal_output<N: TreeNode + Exceptable>(
1148 &mut self,
1149 expr: &LiteralOutput<N>,
1150 ) -> Option<Type> {
1151 self.context.task()?;
1153
1154 for item in expr.items() {
1156 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Output);
1157 }
1158
1159 Some(Type::Hidden(HiddenType::Output))
1160 }
1161
1162 fn evaluate_literal_io_item<N: TreeNode + Exceptable>(
1164 &mut self,
1165 names: impl Iterator<Item = Ident<N::Token>>,
1166 expr: Expr<N>,
1167 io: Io,
1168 ) {
1169 let mut names = names.enumerate().peekable();
1170 let expr_ty = self.evaluate_expr(&expr).unwrap_or(Type::Union);
1171
1172 let mut span = None;
1175 let mut s: Option<&StructType> = None;
1176 while let Some((i, name)) = names.next() {
1177 let ty = if i == 0 {
1179 span = Some(name.span());
1180
1181 match if io == Io::Input {
1182 self.context
1183 .task()
1184 .expect("should have task")
1185 .inputs()
1186 .get(name.text())
1187 .map(|i| i.ty())
1188 } else {
1189 self.context
1190 .task()
1191 .expect("should have task")
1192 .outputs()
1193 .get(name.text())
1194 .map(|o| o.ty())
1195 } {
1196 Some(ty) => ty,
1197 None => {
1198 self.context.add_diagnostic(unknown_task_io(
1199 self.context.task().expect("should have task").name(),
1200 &name,
1201 io,
1202 ));
1203 break;
1204 }
1205 }
1206 } else {
1207 let start = span.unwrap().start();
1209 span = Some(Span::new(start, name.span().end() - start));
1210 let s = s.unwrap();
1211 match s.members().get(name.text()) {
1212 Some(ty) => ty,
1213 None => {
1214 self.context
1215 .add_diagnostic(not_a_struct_member(s.name(), &name));
1216 break;
1217 }
1218 }
1219 };
1220
1221 match ty {
1222 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => s = Some(ty),
1223 _ if names.peek().is_some() => {
1224 self.context.add_diagnostic(not_a_struct(&name, i == 0));
1225 break;
1226 }
1227 _ => {
1228 }
1230 }
1231 }
1232
1233 if let Some((_, last)) = names.last() {
1235 let start = span.unwrap().start();
1236 span = Some(Span::new(start, last.span().end() - start));
1237 }
1238
1239 if !expr_ty.is_coercible_to(&Type::Hidden(HiddenType::Hints)) {
1241 self.context.add_diagnostic(type_mismatch(
1242 &Type::Hidden(HiddenType::Hints),
1243 span.expect("should have span"),
1244 &expr_ty,
1245 expr.span(),
1246 ));
1247 }
1248 }
1249
1250 fn evaluate_if_expr<N: TreeNode + Exceptable>(&mut self, expr: &IfExpr<N>) -> Option<Type> {
1252 let (cond_expr, true_expr, false_expr) = expr.exprs();
1253
1254 let cond_ty = self.evaluate_expr(&cond_expr).unwrap_or(Type::Union);
1256 if !cond_ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1257 self.context
1258 .add_diagnostic(if_conditional_mismatch(&cond_ty, cond_expr.span()));
1259 }
1260
1261 let true_ty = self.evaluate_expr(&true_expr).unwrap_or(Type::Union);
1263 let false_ty = self.evaluate_expr(&false_expr).unwrap_or(Type::Union);
1264
1265 match (true_ty, false_ty) {
1266 (Type::Union, Type::Union) => None,
1267 (Type::Union, false_ty) => Some(false_ty),
1268 (true_ty, Type::Union) => Some(true_ty),
1269 (true_ty, false_ty) => match true_ty.common_type(&false_ty) {
1270 Some(ty) => Some(ty),
1271 _ => {
1272 self.context.add_diagnostic(type_mismatch(
1273 &true_ty,
1274 true_expr.span(),
1275 &false_ty,
1276 false_expr.span(),
1277 ));
1278
1279 None
1280 }
1281 },
1282 }
1283 }
1284
1285 fn evaluate_logical_not_expr<N: TreeNode + Exceptable>(
1287 &mut self,
1288 expr: &LogicalNotExpr<N>,
1289 ) -> Option<Type> {
1290 let operand = expr.operand();
1292 let ty = self.evaluate_expr(&operand).unwrap_or(Type::Union);
1293 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1294 self.context
1295 .add_diagnostic(logical_not_mismatch(&ty, operand.span()));
1296 }
1297
1298 Some(PrimitiveType::Boolean.into())
1299 }
1300
1301 fn evaluate_negation_expr<N: TreeNode + Exceptable>(
1303 &mut self,
1304 expr: &NegationExpr<N>,
1305 ) -> Option<Type> {
1306 let operand = expr.operand();
1308 let ty = self.evaluate_expr(&operand)?;
1309
1310 if ty.eq(&PrimitiveType::Integer.into()) {
1313 return Some(PrimitiveType::Integer.into());
1314 }
1315
1316 if !ty.is_coercible_to(&PrimitiveType::Float.into()) {
1317 self.context
1318 .add_diagnostic(negation_mismatch(&ty, operand.span()));
1319 return None;
1321 }
1322
1323 Some(PrimitiveType::Float.into())
1324 }
1325
1326 fn evaluate_logical_or_expr<N: TreeNode + Exceptable>(
1328 &mut self,
1329 expr: &LogicalOrExpr<N>,
1330 ) -> Option<Type> {
1331 let (lhs, rhs) = expr.operands();
1333
1334 let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
1335 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1336 self.context
1337 .add_diagnostic(logical_or_mismatch(&ty, lhs.span()));
1338 }
1339
1340 let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
1341 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1342 self.context
1343 .add_diagnostic(logical_or_mismatch(&ty, rhs.span()));
1344 }
1345
1346 Some(PrimitiveType::Boolean.into())
1347 }
1348
1349 fn evaluate_logical_and_expr<N: TreeNode + Exceptable>(
1351 &mut self,
1352 expr: &LogicalAndExpr<N>,
1353 ) -> Option<Type> {
1354 let (lhs, rhs) = expr.operands();
1356
1357 let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
1358 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1359 self.context
1360 .add_diagnostic(logical_and_mismatch(&ty, lhs.span()));
1361 }
1362
1363 let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
1364 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1365 self.context
1366 .add_diagnostic(logical_and_mismatch(&ty, rhs.span()));
1367 }
1368
1369 Some(PrimitiveType::Boolean.into())
1370 }
1371
1372 fn evaluate_comparison_expr<N: TreeNode + Exceptable>(
1374 &mut self,
1375 op: ComparisonOperator,
1376 lhs: &Expr<N>,
1377 rhs: &Expr<N>,
1378 span: Span,
1379 ) -> Option<Type> {
1380 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1381 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1382
1383 if lhs_ty.is_union() || lhs_ty.is_none() || rhs_ty.is_union() || rhs_ty.is_none() {
1385 return Some(PrimitiveType::Boolean.into());
1386 }
1387
1388 for expected in [
1390 Type::from(PrimitiveType::Boolean),
1391 PrimitiveType::Integer.into(),
1392 PrimitiveType::Float.into(),
1393 PrimitiveType::String.into(),
1394 PrimitiveType::File.into(),
1395 PrimitiveType::Directory.into(),
1396 ] {
1397 if op != ComparisonOperator::Equality
1399 && op != ComparisonOperator::Inequality
1400 && (matches!(
1401 lhs_ty.as_primitive(),
1402 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1403 ) || matches!(
1404 rhs_ty.as_primitive(),
1405 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1406 ))
1407 {
1408 continue;
1409 }
1410
1411 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1412 return Some(PrimitiveType::Boolean.into());
1413 }
1414
1415 let expected = expected.optional();
1416 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1417 return Some(PrimitiveType::Boolean.into());
1418 }
1419 }
1420
1421 if op == ComparisonOperator::Equality || op == ComparisonOperator::Inequality {
1423 if (lhs_ty.is_coercible_to(&Type::Object) && rhs_ty.is_coercible_to(&Type::Object))
1425 || (lhs_ty.is_coercible_to(&Type::OptionalObject)
1426 && rhs_ty.is_coercible_to(&Type::OptionalObject))
1427 {
1428 return Some(PrimitiveType::Boolean.into());
1429 }
1430
1431 let equal = match (&lhs_ty, &rhs_ty) {
1433 (
1434 Type::Compound(CompoundType::Array(a), _),
1435 Type::Compound(CompoundType::Array(b), _),
1436 ) => a == b,
1437 (
1438 Type::Compound(CompoundType::Pair(a), _),
1439 Type::Compound(CompoundType::Pair(b), _),
1440 ) => a == b,
1441 (
1442 Type::Compound(CompoundType::Map(a), _),
1443 Type::Compound(CompoundType::Map(b), _),
1444 ) => a == b,
1445 (
1446 Type::Compound(CompoundType::Custom(CustomType::Struct(a)), _),
1447 Type::Compound(CompoundType::Custom(CustomType::Struct(b)), _),
1448 ) => a == b,
1449 (
1450 Type::Compound(CompoundType::Custom(CustomType::Enum(a)), _),
1451 Type::Compound(CompoundType::Custom(CustomType::Enum(b)), _),
1452 ) => a == b,
1453 _ => false,
1454 };
1455
1456 if equal {
1457 return Some(PrimitiveType::Boolean.into());
1458 }
1459 }
1460
1461 self.context.add_diagnostic(comparison_mismatch(
1463 op,
1464 span,
1465 &lhs_ty,
1466 lhs.span(),
1467 &rhs_ty,
1468 rhs.span(),
1469 ));
1470 Some(PrimitiveType::Boolean.into())
1471 }
1472
1473 fn evaluate_numeric_expr<N: TreeNode + Exceptable>(
1475 &mut self,
1476 op: NumericOperator,
1477 span: Span,
1478 lhs: &Expr<N>,
1479 rhs: &Expr<N>,
1480 ) -> Option<Type> {
1481 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1482 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1483
1484 if lhs_ty.eq(&PrimitiveType::Integer.into()) && rhs_ty.eq(&PrimitiveType::Integer.into()) {
1486 return Some(PrimitiveType::Integer.into());
1487 }
1488
1489 if !lhs_ty.is_union()
1491 && lhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1492 && !rhs_ty.is_union()
1493 && rhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1494 {
1495 return Some(PrimitiveType::Float.into());
1496 }
1497
1498 if op == NumericOperator::Addition {
1502 let allow_optional = self.placeholders > 0;
1503 let other = if (!lhs_ty.is_optional() || allow_optional)
1504 && lhs_ty
1505 .as_primitive()
1506 .map(|p| p == PrimitiveType::String)
1507 .unwrap_or(false)
1508 {
1509 Some((lhs_ty.is_optional(), &rhs_ty, rhs.span()))
1510 } else if (!rhs_ty.is_optional() || allow_optional)
1511 && rhs_ty
1512 .as_primitive()
1513 .map(|p| p == PrimitiveType::String)
1514 .unwrap_or(false)
1515 {
1516 Some((rhs_ty.is_optional(), &lhs_ty, lhs.span()))
1517 } else {
1518 None
1519 };
1520
1521 if let Some((optional, other, span)) = other {
1522 if (!other.is_optional() || allow_optional)
1523 && other
1524 .as_primitive()
1525 .map(|p| p != PrimitiveType::Boolean)
1526 .unwrap_or(other.is_union() || (allow_optional && other.is_none()))
1527 {
1528 let ty: Type = PrimitiveType::String.into();
1529 if optional || other.is_optional() {
1530 return Some(ty.optional());
1531 }
1532
1533 return Some(ty);
1534 }
1535
1536 self.context
1537 .add_diagnostic(string_concat_mismatch(other, span));
1538 return None;
1539 }
1540 }
1541
1542 if !lhs_ty.is_union() && !rhs_ty.is_union() {
1543 self.context.add_diagnostic(numeric_mismatch(
1544 op,
1545 span,
1546 &lhs_ty,
1547 lhs.span(),
1548 &rhs_ty,
1549 rhs.span(),
1550 ));
1551 }
1552
1553 None
1554 }
1555
1556 fn evaluate_call_expr<N: TreeNode + Exceptable>(&mut self, expr: &CallExpr<N>) -> Option<Type> {
1558 let target = expr.target();
1559 let Some(f) = STDLIB.function(target.text()) else {
1560 self.context
1561 .add_diagnostic(unknown_function(target.text(), target.span()));
1562 return None;
1563 };
1564
1565 let mut count = 0;
1567 let mut arguments = [const { Type::Union }; MAX_PARAMETERS];
1568
1569 for arg in expr.arguments() {
1570 if count < MAX_PARAMETERS {
1571 arguments[count] = self.evaluate_expr(&arg).unwrap_or(Type::Union);
1572 }
1573
1574 count += 1;
1575 }
1576
1577 match target.text() {
1578 "find" | "matches" | "sub" => {
1579 if let Some(Expr::Literal(LiteralExpr::String(pattern_literal))) =
1581 expr.arguments().nth(1)
1582 && let Some(value) = pattern_literal.text()
1583 {
1584 let pattern = value.text().to_string();
1585 if let Err(e) = regex::Regex::new(&pattern) {
1586 self.context.add_diagnostic(invalid_regex_pattern(
1587 target.text(),
1588 value.text(),
1589 &e,
1590 pattern_literal.span(),
1591 ));
1592 }
1593 }
1594 }
1595 _ => {}
1596 }
1597
1598 let arguments = &arguments[..count.min(MAX_PARAMETERS)];
1599 if count <= MAX_PARAMETERS {
1600 match f.bind(self.context.version(), arguments) {
1601 Ok(binding) => {
1602 if let Some(severity) =
1603 self.context.diagnostics_config().unnecessary_function_call
1604 {
1605 self.check_unnecessary_call(expr, arguments, severity);
1606 }
1607 return Some(binding.return_type().clone());
1608 }
1609 Err(FunctionBindError::RequiresVersion(minimum)) => {
1610 self.context.add_diagnostic(unsupported_function(
1611 minimum,
1612 target.text(),
1613 target.span(),
1614 ));
1615 }
1616 Err(FunctionBindError::TooFewArguments(minimum)) => {
1617 self.context.add_diagnostic(too_few_arguments(
1618 target.text(),
1619 target.span(),
1620 minimum,
1621 count,
1622 ));
1623 }
1624 Err(FunctionBindError::TooManyArguments(maximum)) => {
1625 self.context.add_diagnostic(too_many_arguments(
1626 target.text(),
1627 target.span(),
1628 maximum,
1629 count,
1630 expr.arguments().skip(maximum).map(|e| e.span()),
1631 ));
1632 }
1633 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1634 self.context.add_diagnostic(argument_type_mismatch(
1635 target.text(),
1636 &expected,
1637 &arguments[index],
1638 expr.arguments()
1639 .nth(index)
1640 .map(|e| e.span())
1641 .expect("should have span"),
1642 ));
1643 }
1644 Err(FunctionBindError::Ambiguous { first, second }) => {
1645 self.context.add_diagnostic(ambiguous_argument(
1646 target.text(),
1647 target.span(),
1648 &first,
1649 &second,
1650 ));
1651 }
1652 }
1653 } else {
1654 match f.param_min_max(self.context.version()) {
1656 Some((_, max)) => {
1657 assert!(max <= MAX_PARAMETERS);
1658 self.context.add_diagnostic(too_many_arguments(
1659 target.text(),
1660 target.span(),
1661 max,
1662 count,
1663 expr.arguments().skip(max).map(|e| e.span()),
1664 ));
1665 }
1666 None => {
1667 self.context.add_diagnostic(unsupported_function(
1668 f.minimum_version(),
1669 target.text(),
1670 target.span(),
1671 ));
1672 }
1673 }
1674 }
1675
1676 Some(f.realize_unconstrained_return_type(arguments))
1677 }
1678
1679 fn evaluate_index_expr<N: TreeNode + Exceptable>(
1681 &mut self,
1682 expr: &IndexExpr<N>,
1683 ) -> Option<Type> {
1684 let (target, index) = expr.operands();
1685
1686 let target_ty = self.evaluate_expr(&target)?;
1688 let (expected_index_ty, result_ty) = match &target_ty {
1689 Type::Compound(CompoundType::Array(ty), _) => (
1690 Some(PrimitiveType::Integer.into()),
1691 Some(ty.element_type().clone()),
1692 ),
1693 Type::Compound(CompoundType::Map(ty), _) => {
1694 (Some(ty.key_type().clone()), Some(ty.value_type().clone()))
1695 }
1696 _ => (None, None),
1697 };
1698
1699 if let Some(expected_index_ty) = expected_index_ty {
1701 let index_ty = self.evaluate_expr(&index).unwrap_or(Type::Union);
1702 if !index_ty.is_coercible_to(&expected_index_ty) {
1703 self.context.add_diagnostic(index_type_mismatch(
1704 &expected_index_ty,
1705 &index_ty,
1706 index.span(),
1707 ));
1708 }
1709 }
1710
1711 match result_ty {
1712 Some(ty) => Some(ty),
1713 None => {
1714 self.context
1715 .add_diagnostic(cannot_index(&target_ty, target.span()));
1716 None
1717 }
1718 }
1719 }
1720
1721 fn evaluate_access_expr<N: TreeNode + Exceptable>(
1723 &mut self,
1724 expr: &AccessExpr<N>,
1725 ) -> Option<Type> {
1726 let (target, name) = expr.operands();
1727 let ty = self.evaluate_expr(&target)?;
1728
1729 match &ty {
1730 Type::Hidden(HiddenType::TaskPreEvaluation) => {
1731 return match task_member_type_pre_evaluation(name.text()) {
1732 Some(ty) => Some(ty),
1733 None => {
1734 self.context.add_diagnostic(not_a_task_member(&name));
1735 return None;
1736 }
1737 };
1738 }
1739 Type::Hidden(HiddenType::TaskPostEvaluation) => {
1740 return match task_member_type_post_evaluation(self.context.version(), name.text()) {
1741 Some(ty) => Some(ty),
1742 None => {
1743 self.context.add_diagnostic(not_a_task_member(&name));
1744 return None;
1745 }
1746 };
1747 }
1748 Type::Hidden(HiddenType::PreviousTaskData) => {
1749 return match previous_task_data_member_type(name.text()) {
1750 Some(ty) => Some(ty),
1751 None => {
1752 self.context
1753 .add_diagnostic(not_a_previous_task_data_member(&name));
1754 return None;
1755 }
1756 };
1757 }
1758 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => {
1759 if let Some(ty) = ty.members().get(name.text()) {
1760 return Some(ty.clone());
1761 }
1762
1763 self.context
1764 .add_diagnostic(not_a_struct_member(ty.name(), &name));
1765 return None;
1766 }
1767 Type::Compound(CompoundType::Pair(ty), _) => {
1768 return match name.text() {
1770 "left" => Some(ty.left_type().clone()),
1771 "right" => Some(ty.right_type().clone()),
1772 _ => {
1773 self.context.add_diagnostic(not_a_pair_accessor(&name));
1774 None
1775 }
1776 };
1777 }
1778 Type::Call(ty) => {
1779 if let Some(output) = ty.outputs().get(name.text()) {
1780 return Some(output.ty().clone());
1781 }
1782
1783 self.context
1784 .add_diagnostic(unknown_call_io(ty, &name, Io::Output));
1785 return None;
1786 }
1787 Type::TypeNameRef(custom_ty) => match custom_ty {
1788 CustomType::Struct(_) => {
1789 self.context
1790 .add_diagnostic(cannot_access(&ty, target.span()));
1791 return None;
1792 }
1793 CustomType::Enum(_) => {
1794 return Some(Type::from(CompoundType::Custom(custom_ty.clone())));
1795 }
1796 },
1797 _ => {}
1798 }
1799
1800 if ty.is_coercible_to(&Type::OptionalObject) {
1803 return Some(Type::Union);
1804 }
1805
1806 self.context
1807 .add_diagnostic(cannot_access(&ty, target.span()));
1808 None
1809 }
1810
1811 fn check_unnecessary_call<N: TreeNode + Exceptable>(
1813 &mut self,
1814 call: &CallExpr<N>,
1815 arguments: &[Type],
1816 severity: Severity,
1817 ) {
1818 let target = call.target();
1819 let mut arg_spans = call.arguments().map(|arg| arg.span());
1820
1821 let (label, span, fix) = match target.text() {
1822 "select_first" => {
1823 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1824 if ty.is_optional() || ty.is_union() {
1825 return;
1826 }
1827 (
1828 format!("array element {ty:#} is not optional"),
1829 arg_spans.next().expect("should have span"),
1830 "replace the function call with the array's first element",
1831 )
1832 } else {
1833 return;
1834 }
1835 }
1836 "select_all" => {
1837 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1838 if ty.is_optional() || ty.is_union() {
1839 return;
1840 }
1841 (
1842 format!("array element {ty:#} is not optional"),
1843 arg_spans.next().expect("should have span"),
1844 "replace the function call with the array itself",
1845 )
1846 } else {
1847 return;
1848 }
1849 }
1850 "defined" => {
1851 if arguments[0].is_optional() || arguments[0].is_union() {
1852 return;
1853 }
1854
1855 (
1856 format!("{ty:#} is not optional", ty = arguments[0]),
1857 arg_spans.next().expect("should have span"),
1858 "replace the function call with `true`",
1859 )
1860 }
1861 _ => return,
1862 };
1863
1864 self.context.exceptable_add_diagnostic(
1865 unnecessary_function_call(target.text(), target.span(), &label, span)
1866 .with_severity(severity)
1867 .with_fix(fix),
1868 call.inner(),
1869 &UnnecessaryFunctionCall::EXCEPTABLE_NODES,
1870 )
1871 }
1872}