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