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]> {
201 static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
203 Box::new([
204 PrimitiveType::String.into(),
205 STDLIB.array_string_type().clone().into(),
206 ])
207 });
208 const CPU_TYPES: &[Type] = &[
210 Type::Primitive(PrimitiveType::Integer, false),
211 Type::Primitive(PrimitiveType::Float, false),
212 ];
213 const MEMORY_TYPES: &[Type] = &[
215 Type::Primitive(PrimitiveType::Integer, false),
216 Type::Primitive(PrimitiveType::String, false),
217 ];
218 const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
220 const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
222 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
224 Box::new([
225 PrimitiveType::Integer.into(),
226 PrimitiveType::String.into(),
227 STDLIB.array_string_type().clone().into(),
228 ])
229 });
230 const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
232 static RETURN_CODES_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
234 Box::new([
235 PrimitiveType::Integer.into(),
236 PrimitiveType::String.into(),
237 STDLIB.array_int_type().clone().into(),
238 ])
239 });
240
241 match name {
248 TASK_REQUIREMENT_CONTAINER | TASK_REQUIREMENT_CONTAINER_ALIAS => Some(&CONTAINER_TYPES),
249 TASK_REQUIREMENT_CPU if version >= SupportedVersion::V1(V1::One) => Some(CPU_TYPES),
250 TASK_REQUIREMENT_DISKS if version >= SupportedVersion::V1(V1::One) => Some(&DISKS_TYPES),
251 TASK_REQUIREMENT_GPU if version >= SupportedVersion::V1(V1::One) => Some(GPU_TYPES),
252 TASK_REQUIREMENT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
253 TASK_REQUIREMENT_MAX_RETRIES if version >= SupportedVersion::V1(V1::Two) => {
254 Some(MAX_RETRIES_TYPES)
255 }
256 TASK_REQUIREMENT_MAX_RETRIES_ALIAS if version >= SupportedVersion::V1(V1::One) => {
257 Some(MAX_RETRIES_TYPES)
258 }
259 TASK_REQUIREMENT_MEMORY => Some(MEMORY_TYPES),
260 TASK_REQUIREMENT_RETURN_CODES if version >= SupportedVersion::V1(V1::Two) => {
261 Some(&RETURN_CODES_TYPES)
262 }
263 TASK_REQUIREMENT_RETURN_CODES_ALIAS if version >= SupportedVersion::V1(V1::One) => {
264 Some(&RETURN_CODES_TYPES)
265 }
266 _ => None,
267 }
268}
269
270pub fn task_hint_types(
274 version: SupportedVersion,
275 name: &str,
276 use_hidden_types: bool,
277) -> Option<&'static [Type]> {
278 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
280 Box::new([
281 PrimitiveType::String.into(),
282 STDLIB.map_string_string_type().clone().into(),
283 ])
284 });
285 const FPGA_TYPES: &[Type] = &[
287 Type::Primitive(PrimitiveType::Integer, false),
288 Type::Primitive(PrimitiveType::String, false),
289 ];
290 const GPU_TYPES: &[Type] = &[
292 Type::Primitive(PrimitiveType::Integer, false),
293 Type::Primitive(PrimitiveType::String, false),
294 ];
295 const INPUTS_TYPES: &[Type] = &[Type::Object];
297 const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Input)];
299 const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
301 const MAX_CPU_TYPES: &[Type] = &[
303 Type::Primitive(PrimitiveType::Integer, false),
304 Type::Primitive(PrimitiveType::Float, false),
305 ];
306 const MAX_MEMORY_TYPES: &[Type] = &[
308 Type::Primitive(PrimitiveType::Integer, false),
309 Type::Primitive(PrimitiveType::String, false),
310 ];
311 const OUTPUTS_TYPES: &[Type] = &[Type::Object];
313 const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Output)];
315 const SHORT_TASK_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
317 const CACHEABLE_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
319
320 match name {
321 TASK_HINT_DISKS => Some(&DISKS_TYPES),
322 TASK_HINT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
323 TASK_HINT_GPU => Some(GPU_TYPES),
324 TASK_HINT_INPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
325 Some(INPUTS_HIDDEN_TYPES)
326 }
327 TASK_HINT_INPUTS => Some(INPUTS_TYPES),
328 TASK_HINT_LOCALIZATION_OPTIONAL if version >= SupportedVersion::V1(V1::Two) => {
329 Some(LOCALIZATION_OPTIONAL_TYPES)
330 }
331 TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS => Some(LOCALIZATION_OPTIONAL_TYPES),
332 TASK_HINT_MAX_CPU if version >= SupportedVersion::V1(V1::Two) => Some(MAX_CPU_TYPES),
333 TASK_HINT_MAX_CPU_ALIAS => Some(MAX_CPU_TYPES),
334 TASK_HINT_MAX_MEMORY if version >= SupportedVersion::V1(V1::Two) => Some(MAX_MEMORY_TYPES),
335 TASK_HINT_MAX_MEMORY_ALIAS => Some(MAX_MEMORY_TYPES),
336 TASK_HINT_OUTPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
337 Some(OUTPUTS_HIDDEN_TYPES)
338 }
339 TASK_HINT_OUTPUTS => Some(OUTPUTS_TYPES),
340 TASK_HINT_SHORT_TASK if version >= SupportedVersion::V1(V1::Two) => Some(SHORT_TASK_TYPES),
341 TASK_HINT_SHORT_TASK_ALIAS => Some(SHORT_TASK_TYPES),
342 TASK_HINT_CACHEABLE => Some(CACHEABLE_TYPES),
343 _ => None,
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum ComparisonOperator {
350 Equality,
352 Inequality,
354 Less,
356 LessEqual,
358 Greater,
360 GreaterEqual,
362}
363
364impl fmt::Display for ComparisonOperator {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 write!(
367 f,
368 "{}",
369 match self {
370 Self::Equality => "==",
371 Self::Inequality => "!=",
372 Self::Less => "<",
373 Self::LessEqual => "<=",
374 Self::Greater => ">",
375 Self::GreaterEqual => ">=",
376 }
377 )
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum NumericOperator {
384 Addition,
386 Subtraction,
388 Multiplication,
390 Division,
392 Modulo,
394 Exponentiation,
396}
397
398impl fmt::Display for NumericOperator {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 write!(
401 f,
402 "{}",
403 match self {
404 Self::Addition => "addition",
405 Self::Subtraction => "subtraction",
406 Self::Multiplication => "multiplication",
407 Self::Division => "division",
408 Self::Modulo => "remainder",
409 Self::Exponentiation => "exponentiation",
410 }
411 )
412 }
413}
414
415#[derive(Debug)]
417pub struct AstTypeConverter<R>(R);
418
419impl<R> AstTypeConverter<R>
420where
421 R: TypeNameResolver,
422{
423 pub fn new(resolver: R) -> Self {
425 Self(resolver)
426 }
427
428 pub fn convert_type<N: TreeNode>(&mut self, ty: &v1::Type<N>) -> Result<Type, Diagnostic> {
433 let optional = ty.is_optional();
434
435 let ty: Type = match ty {
436 v1::Type::Map(ty) => {
437 let ty = self.convert_map_type(ty)?;
438 ty.into()
439 }
440 v1::Type::Array(ty) => {
441 let ty = self.convert_array_type(ty)?;
442 ty.into()
443 }
444 v1::Type::Pair(ty) => {
445 let ty = self.convert_pair_type(ty)?;
446 ty.into()
447 }
448 v1::Type::Object(_) => Type::Object,
449 v1::Type::Ref(r) => {
450 let name = r.name();
451 self.0.resolve(name.text(), name.span())?
452 }
453 v1::Type::Primitive(ty) => Type::Primitive(ty.kind().into(), false),
454 };
455
456 if optional { Ok(ty.optional()) } else { Ok(ty) }
457 }
458
459 pub fn convert_array_type<N: TreeNode>(
464 &mut self,
465 ty: &v1::ArrayType<N>,
466 ) -> Result<ArrayType, Diagnostic> {
467 let element_type = self.convert_type(&ty.element_type())?;
468 if ty.is_non_empty() {
469 Ok(ArrayType::non_empty(element_type))
470 } else {
471 Ok(ArrayType::new(element_type))
472 }
473 }
474
475 pub fn convert_pair_type<N: TreeNode>(
480 &mut self,
481 ty: &v1::PairType<N>,
482 ) -> Result<PairType, Diagnostic> {
483 let (left_type, right_type) = ty.types();
484 Ok(PairType::new(
485 self.convert_type(&left_type)?,
486 self.convert_type(&right_type)?,
487 ))
488 }
489
490 pub fn convert_map_type<N: TreeNode>(
495 &mut self,
496 ty: &v1::MapType<N>,
497 ) -> Result<MapType, Diagnostic> {
498 let (key_type, value_type) = ty.types();
499 let key_type =
500 Type::Primitive(PrimitiveType::from(key_type.kind()), key_type.is_optional());
501
502 if key_type.is_optional() {
504 return Err(map_key_not_primitive(ty.types().0.span(), &key_type));
505 }
506
507 Ok(MapType::new(key_type, self.convert_type(&value_type)?))
508 }
509
510 pub fn convert_struct_type<N: TreeNode>(
515 &mut self,
516 definition: &v1::StructDefinition<N>,
517 ) -> Result<StructType, Diagnostic> {
518 Ok(StructType::new(
519 definition.name().text().to_string(),
520 definition
521 .members()
522 .map(|d| Ok((d.name().text().to_string(), self.convert_type(&d.ty())?)))
523 .collect::<Result<Vec<_>, Diagnostic>>()?,
524 ))
525 }
526}
527
528impl From<v1::PrimitiveTypeKind> for PrimitiveType {
529 fn from(value: v1::PrimitiveTypeKind) -> Self {
530 match value {
531 v1::PrimitiveTypeKind::Boolean => Self::Boolean,
532 v1::PrimitiveTypeKind::Integer => Self::Integer,
533 v1::PrimitiveTypeKind::Float => Self::Float,
534 v1::PrimitiveTypeKind::String => Self::String,
535 v1::PrimitiveTypeKind::File => Self::File,
536 v1::PrimitiveTypeKind::Directory => Self::Directory,
537 }
538 }
539}
540
541pub trait EvaluationContext {
543 fn version(&self) -> SupportedVersion;
545
546 fn resolve_name(&mut self, name: &str, span: Span) -> Option<Type>;
555
556 fn resolve_type_name(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
561
562 fn task(&self) -> Option<&Task>;
566
567 fn diagnostics_config(&self) -> DiagnosticsConfig;
569
570 fn add_diagnostic(&mut self, diagnostic: Diagnostic);
572
573 fn exceptable_add_diagnostic<N: TreeNode + Exceptable>(
576 &mut self,
577 diagnostic: Diagnostic,
578 element: &N,
579 exceptable_nodes: &Option<&'static [SyntaxKind]>,
580 );
581}
582
583#[derive(Debug)]
585pub struct ExprTypeEvaluator<'a, C> {
586 context: &'a mut C,
588 placeholders: usize,
596}
597
598impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
599 pub fn new(context: &'a mut C) -> Self {
601 Self {
602 context,
603 placeholders: 0,
604 }
605 }
606
607 pub fn evaluate_expr<N: TreeNode + Exceptable>(&mut self, expr: &Expr<N>) -> Option<Type> {
611 match expr {
612 Expr::Literal(expr) => self.evaluate_literal_expr(expr),
613 Expr::NameRef(r) => {
614 let name = r.name();
615 self.context.resolve_name(name.text(), name.span())
616 }
617 Expr::Parenthesized(expr) => self.evaluate_expr(&expr.expr()),
618 Expr::If(expr) => self.evaluate_if_expr(expr),
619 Expr::LogicalNot(expr) => self.evaluate_logical_not_expr(expr),
620 Expr::Negation(expr) => self.evaluate_negation_expr(expr),
621 Expr::LogicalOr(expr) => self.evaluate_logical_or_expr(expr),
622 Expr::LogicalAnd(expr) => self.evaluate_logical_and_expr(expr),
623 Expr::Equality(expr) => {
624 let (lhs, rhs) = expr.operands();
625 self.evaluate_comparison_expr(ComparisonOperator::Equality, &lhs, &rhs, expr.span())
626 }
627 Expr::Inequality(expr) => {
628 let (lhs, rhs) = expr.operands();
629 self.evaluate_comparison_expr(
630 ComparisonOperator::Inequality,
631 &lhs,
632 &rhs,
633 expr.span(),
634 )
635 }
636 Expr::Less(expr) => {
637 let (lhs, rhs) = expr.operands();
638 self.evaluate_comparison_expr(ComparisonOperator::Less, &lhs, &rhs, expr.span())
639 }
640 Expr::LessEqual(expr) => {
641 let (lhs, rhs) = expr.operands();
642 self.evaluate_comparison_expr(
643 ComparisonOperator::LessEqual,
644 &lhs,
645 &rhs,
646 expr.span(),
647 )
648 }
649 Expr::Greater(expr) => {
650 let (lhs, rhs) = expr.operands();
651 self.evaluate_comparison_expr(ComparisonOperator::Greater, &lhs, &rhs, expr.span())
652 }
653 Expr::GreaterEqual(expr) => {
654 let (lhs, rhs) = expr.operands();
655 self.evaluate_comparison_expr(
656 ComparisonOperator::GreaterEqual,
657 &lhs,
658 &rhs,
659 expr.span(),
660 )
661 }
662 Expr::Addition(expr) => {
663 let (lhs, rhs) = expr.operands();
664 self.evaluate_numeric_expr(NumericOperator::Addition, expr.span(), &lhs, &rhs)
665 }
666 Expr::Subtraction(expr) => {
667 let (lhs, rhs) = expr.operands();
668 self.evaluate_numeric_expr(NumericOperator::Subtraction, expr.span(), &lhs, &rhs)
669 }
670 Expr::Multiplication(expr) => {
671 let (lhs, rhs) = expr.operands();
672 self.evaluate_numeric_expr(NumericOperator::Multiplication, expr.span(), &lhs, &rhs)
673 }
674 Expr::Division(expr) => {
675 let (lhs, rhs) = expr.operands();
676 self.evaluate_numeric_expr(NumericOperator::Division, expr.span(), &lhs, &rhs)
677 }
678 Expr::Modulo(expr) => {
679 let (lhs, rhs) = expr.operands();
680 self.evaluate_numeric_expr(NumericOperator::Modulo, expr.span(), &lhs, &rhs)
681 }
682 Expr::Exponentiation(expr) => {
683 let (lhs, rhs) = expr.operands();
684 self.evaluate_numeric_expr(NumericOperator::Exponentiation, expr.span(), &lhs, &rhs)
685 }
686 Expr::Call(expr) => self.evaluate_call_expr(expr),
687 Expr::Index(expr) => self.evaluate_index_expr(expr),
688 Expr::Access(expr) => self.evaluate_access_expr(expr),
689 }
690 }
691
692 fn evaluate_literal_expr<N: TreeNode + Exceptable>(
694 &mut self,
695 expr: &LiteralExpr<N>,
696 ) -> Option<Type> {
697 match expr {
698 LiteralExpr::Boolean(_) => Some(PrimitiveType::Boolean.into()),
699 LiteralExpr::Integer(_) => Some(PrimitiveType::Integer.into()),
700 LiteralExpr::Float(_) => Some(PrimitiveType::Float.into()),
701 LiteralExpr::String(s) => {
702 for p in s.parts() {
703 if let StringPart::Placeholder(p) = p {
704 self.check_placeholder(&p);
705 }
706 }
707
708 Some(PrimitiveType::String.into())
709 }
710 LiteralExpr::Array(expr) => Some(self.evaluate_literal_array(expr)),
711 LiteralExpr::Pair(expr) => Some(self.evaluate_literal_pair(expr)),
712 LiteralExpr::Map(expr) => Some(self.evaluate_literal_map(expr)),
713 LiteralExpr::Object(expr) => Some(self.evaluate_literal_object(expr)),
714 LiteralExpr::Struct(expr) => self.evaluate_literal_struct(expr),
715 LiteralExpr::None(_) => Some(Type::None),
716 LiteralExpr::Hints(expr) => self.evaluate_literal_hints(expr),
717 LiteralExpr::Input(expr) => self.evaluate_literal_input(expr),
718 LiteralExpr::Output(expr) => self.evaluate_literal_output(expr),
719 }
720 }
721
722 pub(crate) fn check_placeholder<N: TreeNode + Exceptable>(
724 &mut self,
725 placeholder: &Placeholder<N>,
726 ) {
727 self.placeholders += 1;
728
729 let expr = placeholder.expr();
732 if let Some(ty) = self.evaluate_expr(&expr) {
733 if let Some(option) = placeholder.option() {
734 let valid = match option {
735 PlaceholderOption::Sep(_) => {
736 ty == Type::Union
737 || ty == Type::None
738 || matches!(&ty,
739 Type::Compound(CompoundType::Array(array_ty), _)
740 if matches!(array_ty.element_type(), Type::Primitive(_, false) | Type::Union))
741 }
742 PlaceholderOption::Default(_) => {
743 matches!(ty, Type::Primitive(..) | Type::Union | Type::None)
744 }
745 PlaceholderOption::TrueFalse(_) => {
746 matches!(
747 ty,
748 Type::Primitive(PrimitiveType::Boolean, _) | Type::Union | Type::None
749 )
750 }
751 };
752
753 if !valid {
754 self.context.add_diagnostic(invalid_placeholder_option(
755 &ty,
756 expr.span(),
757 &option,
758 ));
759 }
760 } else {
761 match ty {
762 Type::Primitive(..)
763 | Type::Union
764 | Type::None
765 | Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _) => {}
766 _ => {
767 self.context
768 .add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
769 }
770 }
771 }
772 }
773
774 self.placeholders -= 1;
775 }
776
777 fn evaluate_literal_array<N: TreeNode + Exceptable>(&mut self, expr: &LiteralArray<N>) -> Type {
779 let mut elements = expr.elements();
782 match elements
783 .next()
784 .and_then(|e| Some((self.evaluate_expr(&e)?, e.span())))
785 {
786 Some((mut expected, mut expected_span)) => {
787 for expr in elements {
789 if let Some(actual) = self.evaluate_expr(&expr) {
790 match expected.common_type(&actual) {
791 Some(ty) => {
792 expected = ty;
793 expected_span = expr.span();
794 }
795 _ => {
796 self.context.add_diagnostic(no_common_type(
797 &expected,
798 expected_span,
799 &actual,
800 expr.span(),
801 ));
802 }
803 }
804 }
805 }
806
807 ArrayType::new(expected).into()
808 }
809 None => ArrayType::new(Type::Union).into(),
811 }
812 }
813
814 fn evaluate_literal_pair<N: TreeNode + Exceptable>(&mut self, expr: &LiteralPair<N>) -> Type {
816 let (left, right) = expr.exprs();
817 let left = self.evaluate_expr(&left).unwrap_or(Type::Union);
818 let right = self.evaluate_expr(&right).unwrap_or(Type::Union);
819 PairType::new(left, right).into()
820 }
821
822 fn evaluate_literal_map<N: TreeNode + Exceptable>(&mut self, expr: &LiteralMap<N>) -> Type {
824 let map_item_type = |item: LiteralMapItem<N>| {
825 let (key, value) = item.key_value();
826 let expected_key = self.evaluate_expr(&key)?;
827 match expected_key {
828 Type::Primitive(_, false) | Type::Union => {
829 }
831 _ => {
832 self.context
833 .add_diagnostic(map_key_not_primitive(key.span(), &expected_key));
834 return None;
835 }
836 }
837
838 Some((
839 expected_key,
840 key.span(),
841 self.evaluate_expr(&value)?,
842 value.span(),
843 ))
844 };
845
846 let mut items = expr.items();
847 match items.next().and_then(map_item_type) {
848 Some((
849 mut expected_key,
850 mut expected_key_span,
851 mut expected_value,
852 mut expected_value_span,
853 )) => {
854 for item in items {
856 let (key, value) = item.key_value();
857 if let Some(actual_key) = self.evaluate_expr(&key)
858 && let Some(actual_value) = self.evaluate_expr(&value)
859 {
860 match actual_key {
862 Type::Primitive(_, false) | Type::Union => {
863 match expected_key.common_type(&actual_key) {
864 Some(ty) => {
865 expected_key = ty;
866 expected_key_span = key.span();
867 }
868 _ => {
869 self.context.add_diagnostic(no_common_type(
870 &expected_key,
871 expected_key_span,
872 &actual_key,
873 key.span(),
874 ));
875 }
876 }
877 }
878 _ => {
879 self.context
880 .add_diagnostic(map_key_not_primitive(key.span(), &actual_key));
881 }
882 }
883
884 match expected_value.common_type(&actual_value) {
885 Some(ty) => {
886 expected_value = ty;
887 expected_value_span = value.span();
888 }
889 _ => {
890 self.context.add_diagnostic(no_common_type(
891 &expected_value,
892 expected_value_span,
893 &actual_value,
894 value.span(),
895 ));
896 }
897 }
898 }
899 }
900
901 MapType::new(expected_key, expected_value).into()
902 }
903 None => MapType::new(Type::Union, Type::Union).into(),
905 }
906 }
907
908 fn evaluate_literal_object<N: TreeNode + Exceptable>(
910 &mut self,
911 expr: &LiteralObject<N>,
912 ) -> Type {
913 for item in expr.items() {
915 let (_, v) = item.name_value();
916 self.evaluate_expr(&v);
917 }
918
919 Type::Object
920 }
921
922 fn evaluate_literal_struct<N: TreeNode + Exceptable>(
924 &mut self,
925 expr: &LiteralStruct<N>,
926 ) -> Option<Type> {
927 let name = expr.name();
928 match self.context.resolve_type_name(name.text(), name.span()) {
929 Ok(ty) => {
930 let ty = match &ty {
931 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), false) => ty,
932 _ => panic!("type should be a required struct"),
933 };
934
935 let mut present = vec![false; ty.members().len()];
937
938 for item in expr.items() {
940 let (n, v) = item.name_value();
941 match ty.members().get_full(n.text()) {
942 Some((index, _, expected)) => {
943 present[index] = true;
944 if let Some(actual) = self.evaluate_expr(&v)
945 && !actual.is_coercible_to(expected)
946 {
947 self.context.add_diagnostic(type_mismatch(
948 expected,
949 n.span(),
950 &actual,
951 v.span(),
952 ));
953 }
954 }
955 _ => {
956 self.context
958 .add_diagnostic(not_a_struct_member(name.text(), &n));
959 }
960 }
961 }
962
963 let mut unspecified = present
965 .iter()
966 .enumerate()
967 .filter_map(|(i, present)| {
968 if *present {
969 return None;
970 }
971
972 let (name, member_ty) = ty.members().get_index(i).unwrap();
973 if member_ty.is_optional() {
974 return None;
975 }
976
977 Some(name.as_str())
978 })
979 .peekable();
980
981 if unspecified.peek().is_some() {
982 let mut members = String::new();
983 let mut count = 0;
984 while let Some(member) = unspecified.next() {
985 match (unspecified.peek().is_none(), count) {
986 (true, c) if c > 1 => members.push_str(", and "),
987 (true, 1) => members.push_str(" and "),
988 (false, c) if c > 0 => members.push_str(", "),
989 _ => {}
990 }
991
992 write!(&mut members, "`{member}`").ok();
993 count += 1;
994 }
995
996 self.context
997 .add_diagnostic(missing_struct_members(&name, count, &members));
998 }
999
1000 Some(Type::Compound(
1001 CompoundType::Custom(CustomType::Struct(ty.clone())),
1002 false,
1003 ))
1004 }
1005 Err(diagnostic) => {
1006 self.context.add_diagnostic(diagnostic);
1007 None
1008 }
1009 }
1010 }
1011
1012 pub(crate) fn evaluate_runtime_item<N: TreeNode + Exceptable>(
1014 &mut self,
1015 name: &Ident<N::Token>,
1016 expr: &Expr<N>,
1017 ) {
1018 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1019
1020 if self.context.version() < SupportedVersion::V1(V1::One)
1031 && matches!(
1032 name.text(),
1033 TASK_REQUIREMENT_CPU
1034 | TASK_REQUIREMENT_GPU
1035 | TASK_REQUIREMENT_DISKS
1036 | TASK_REQUIREMENT_MAX_RETRIES_ALIAS
1037 | TASK_REQUIREMENT_RETURN_CODES_ALIAS
1038 )
1039 {
1040 return;
1041 }
1042
1043 if !self.evaluate_requirement(name, expr, &expr_ty) {
1044 if let Some(expected) = task_hint_types(self.context.version(), name.text(), false)
1047 && !expected
1048 .iter()
1049 .any(|target| expr_ty.is_coercible_to(target))
1050 {
1051 self.context.add_diagnostic(multiple_type_mismatch(
1052 expected,
1053 name.span(),
1054 &expr_ty,
1055 expr.span(),
1056 ));
1057 }
1058 }
1059 }
1060
1061 pub(crate) fn evaluate_requirements_item<N: TreeNode + Exceptable>(
1063 &mut self,
1064 name: &Ident<N::Token>,
1065 expr: &Expr<N>,
1066 ) {
1067 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1068 self.evaluate_requirement(name, expr, &expr_ty);
1069 }
1070
1071 fn evaluate_requirement<N: TreeNode>(
1077 &mut self,
1078 name: &Ident<N::Token>,
1079 expr: &Expr<N>,
1080 expr_ty: &Type,
1081 ) -> bool {
1082 if let Some(expected) = task_requirement_types(self.context.version(), name.text()) {
1083 if !expected
1084 .iter()
1085 .any(|target| expr_ty.is_coercible_to(target))
1086 {
1087 self.context.add_diagnostic(multiple_type_mismatch(
1088 expected,
1089 name.span(),
1090 expr_ty,
1091 expr.span(),
1092 ));
1093 }
1094
1095 return true;
1096 }
1097
1098 false
1099 }
1100
1101 fn evaluate_literal_hints<N: TreeNode + Exceptable>(
1103 &mut self,
1104 expr: &LiteralHints<N>,
1105 ) -> Option<Type> {
1106 self.context.task()?;
1107
1108 for item in expr.items() {
1109 self.evaluate_hints_item(&item.name(), &item.expr())
1110 }
1111
1112 Some(Type::Hidden(HiddenType::Hints))
1113 }
1114
1115 pub(crate) fn evaluate_hints_item<N: TreeNode + Exceptable>(
1118 &mut self,
1119 name: &Ident<N::Token>,
1120 expr: &Expr<N>,
1121 ) {
1122 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1123 if let Some(expected) = task_hint_types(self.context.version(), name.text(), true)
1124 && !expected
1125 .iter()
1126 .any(|target| expr_ty.is_coercible_to(target))
1127 {
1128 self.context.add_diagnostic(multiple_type_mismatch(
1129 expected,
1130 name.span(),
1131 &expr_ty,
1132 expr.span(),
1133 ));
1134 }
1135 }
1136
1137 fn evaluate_literal_input<N: TreeNode + Exceptable>(
1139 &mut self,
1140 expr: &LiteralInput<N>,
1141 ) -> Option<Type> {
1142 self.context.task()?;
1144
1145 for item in expr.items() {
1147 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Input);
1148 }
1149
1150 Some(Type::Hidden(HiddenType::Input))
1151 }
1152
1153 fn evaluate_literal_output<N: TreeNode + Exceptable>(
1155 &mut self,
1156 expr: &LiteralOutput<N>,
1157 ) -> Option<Type> {
1158 self.context.task()?;
1160
1161 for item in expr.items() {
1163 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Output);
1164 }
1165
1166 Some(Type::Hidden(HiddenType::Output))
1167 }
1168
1169 fn evaluate_literal_io_item<N: TreeNode + Exceptable>(
1171 &mut self,
1172 names: impl Iterator<Item = Ident<N::Token>>,
1173 expr: Expr<N>,
1174 io: Io,
1175 ) {
1176 let mut names = names.enumerate().peekable();
1177 let expr_ty = self.evaluate_expr(&expr).unwrap_or(Type::Union);
1178
1179 let mut span = None;
1182 let mut s: Option<&StructType> = None;
1183 while let Some((i, name)) = names.next() {
1184 let ty = if i == 0 {
1186 span = Some(name.span());
1187
1188 match if io == Io::Input {
1189 self.context
1190 .task()
1191 .expect("should have task")
1192 .inputs()
1193 .get(name.text())
1194 .map(|i| i.ty())
1195 } else {
1196 self.context
1197 .task()
1198 .expect("should have task")
1199 .outputs()
1200 .get(name.text())
1201 .map(|o| o.ty())
1202 } {
1203 Some(ty) => ty,
1204 None => {
1205 self.context.add_diagnostic(unknown_task_io(
1206 self.context.task().expect("should have task").name(),
1207 &name,
1208 io,
1209 ));
1210 break;
1211 }
1212 }
1213 } else {
1214 let start = span.unwrap().start();
1216 span = Some(Span::new(start, name.span().end() - start));
1217 let s = s.unwrap();
1218 match s.members().get(name.text()) {
1219 Some(ty) => ty,
1220 None => {
1221 self.context
1222 .add_diagnostic(not_a_struct_member(s.name(), &name));
1223 break;
1224 }
1225 }
1226 };
1227
1228 match ty {
1229 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => s = Some(ty),
1230 _ if names.peek().is_some() => {
1231 self.context.add_diagnostic(not_a_struct(&name, i == 0));
1232 break;
1233 }
1234 _ => {
1235 }
1237 }
1238 }
1239
1240 if let Some((_, last)) = names.last() {
1242 let start = span.unwrap().start();
1243 span = Some(Span::new(start, last.span().end() - start));
1244 }
1245
1246 if !expr_ty.is_coercible_to(&Type::Hidden(HiddenType::Hints)) {
1248 self.context.add_diagnostic(type_mismatch(
1249 &Type::Hidden(HiddenType::Hints),
1250 span.expect("should have span"),
1251 &expr_ty,
1252 expr.span(),
1253 ));
1254 }
1255 }
1256
1257 fn evaluate_if_expr<N: TreeNode + Exceptable>(&mut self, expr: &IfExpr<N>) -> Option<Type> {
1259 let (cond_expr, true_expr, false_expr) = expr.exprs();
1260
1261 let cond_ty = self.evaluate_expr(&cond_expr).unwrap_or(Type::Union);
1263 if !cond_ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1264 self.context
1265 .add_diagnostic(if_conditional_mismatch(&cond_ty, cond_expr.span()));
1266 }
1267
1268 let true_ty = self.evaluate_expr(&true_expr).unwrap_or(Type::Union);
1270 let false_ty = self.evaluate_expr(&false_expr).unwrap_or(Type::Union);
1271
1272 match (true_ty, false_ty) {
1273 (Type::Union, Type::Union) => None,
1274 (Type::Union, false_ty) => Some(false_ty),
1275 (true_ty, Type::Union) => Some(true_ty),
1276 (true_ty, false_ty) => match true_ty.common_type(&false_ty) {
1277 Some(ty) => Some(ty),
1278 _ => {
1279 self.context.add_diagnostic(type_mismatch(
1280 &true_ty,
1281 true_expr.span(),
1282 &false_ty,
1283 false_expr.span(),
1284 ));
1285
1286 None
1287 }
1288 },
1289 }
1290 }
1291
1292 fn evaluate_logical_not_expr<N: TreeNode + Exceptable>(
1294 &mut self,
1295 expr: &LogicalNotExpr<N>,
1296 ) -> Option<Type> {
1297 let operand = expr.operand();
1299 let ty = self.evaluate_expr(&operand).unwrap_or(Type::Union);
1300 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1301 self.context
1302 .add_diagnostic(logical_not_mismatch(&ty, operand.span()));
1303 }
1304
1305 Some(PrimitiveType::Boolean.into())
1306 }
1307
1308 fn evaluate_negation_expr<N: TreeNode + Exceptable>(
1310 &mut self,
1311 expr: &NegationExpr<N>,
1312 ) -> Option<Type> {
1313 let operand = expr.operand();
1315 let ty = self.evaluate_expr(&operand)?;
1316
1317 if ty.eq(&PrimitiveType::Integer.into()) {
1320 return Some(PrimitiveType::Integer.into());
1321 }
1322
1323 if !ty.is_coercible_to(&PrimitiveType::Float.into()) {
1324 self.context
1325 .add_diagnostic(negation_mismatch(&ty, operand.span()));
1326 return None;
1328 }
1329
1330 Some(PrimitiveType::Float.into())
1331 }
1332
1333 fn evaluate_logical_or_expr<N: TreeNode + Exceptable>(
1335 &mut self,
1336 expr: &LogicalOrExpr<N>,
1337 ) -> Option<Type> {
1338 let (lhs, rhs) = expr.operands();
1340
1341 let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
1342 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1343 self.context
1344 .add_diagnostic(logical_or_mismatch(&ty, lhs.span()));
1345 }
1346
1347 let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
1348 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1349 self.context
1350 .add_diagnostic(logical_or_mismatch(&ty, rhs.span()));
1351 }
1352
1353 Some(PrimitiveType::Boolean.into())
1354 }
1355
1356 fn evaluate_logical_and_expr<N: TreeNode + Exceptable>(
1358 &mut self,
1359 expr: &LogicalAndExpr<N>,
1360 ) -> Option<Type> {
1361 let (lhs, rhs) = expr.operands();
1363
1364 let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
1365 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1366 self.context
1367 .add_diagnostic(logical_and_mismatch(&ty, lhs.span()));
1368 }
1369
1370 let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
1371 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1372 self.context
1373 .add_diagnostic(logical_and_mismatch(&ty, rhs.span()));
1374 }
1375
1376 Some(PrimitiveType::Boolean.into())
1377 }
1378
1379 fn evaluate_comparison_expr<N: TreeNode + Exceptable>(
1381 &mut self,
1382 op: ComparisonOperator,
1383 lhs: &Expr<N>,
1384 rhs: &Expr<N>,
1385 span: Span,
1386 ) -> Option<Type> {
1387 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1388 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1389
1390 if lhs_ty.is_union() || lhs_ty.is_none() || rhs_ty.is_union() || rhs_ty.is_none() {
1392 return Some(PrimitiveType::Boolean.into());
1393 }
1394
1395 for expected in [
1397 Type::from(PrimitiveType::Boolean),
1398 PrimitiveType::Integer.into(),
1399 PrimitiveType::Float.into(),
1400 PrimitiveType::String.into(),
1401 PrimitiveType::File.into(),
1402 PrimitiveType::Directory.into(),
1403 ] {
1404 if op != ComparisonOperator::Equality
1406 && op != ComparisonOperator::Inequality
1407 && (matches!(
1408 lhs_ty.as_primitive(),
1409 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1410 ) || matches!(
1411 rhs_ty.as_primitive(),
1412 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1413 ))
1414 {
1415 continue;
1416 }
1417
1418 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1419 return Some(PrimitiveType::Boolean.into());
1420 }
1421
1422 let expected = expected.optional();
1423 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1424 return Some(PrimitiveType::Boolean.into());
1425 }
1426 }
1427
1428 if op == ComparisonOperator::Equality || op == ComparisonOperator::Inequality {
1430 if (lhs_ty.is_coercible_to(&Type::Object) && rhs_ty.is_coercible_to(&Type::Object))
1432 || (lhs_ty.is_coercible_to(&Type::OptionalObject)
1433 && rhs_ty.is_coercible_to(&Type::OptionalObject))
1434 {
1435 return Some(PrimitiveType::Boolean.into());
1436 }
1437
1438 let equal = match (&lhs_ty, &rhs_ty) {
1440 (
1441 Type::Compound(CompoundType::Array(a), _),
1442 Type::Compound(CompoundType::Array(b), _),
1443 ) => a == b,
1444 (
1445 Type::Compound(CompoundType::Pair(a), _),
1446 Type::Compound(CompoundType::Pair(b), _),
1447 ) => a == b,
1448 (
1449 Type::Compound(CompoundType::Map(a), _),
1450 Type::Compound(CompoundType::Map(b), _),
1451 ) => a == b,
1452 (
1453 Type::Compound(CompoundType::Custom(CustomType::Struct(a)), _),
1454 Type::Compound(CompoundType::Custom(CustomType::Struct(b)), _),
1455 ) => a == b,
1456 (
1457 Type::Compound(CompoundType::Custom(CustomType::Enum(a)), _),
1458 Type::Compound(CompoundType::Custom(CustomType::Enum(b)), _),
1459 ) => a == b,
1460 _ => false,
1461 };
1462
1463 if equal {
1464 return Some(PrimitiveType::Boolean.into());
1465 }
1466 }
1467
1468 self.context.add_diagnostic(comparison_mismatch(
1470 op,
1471 span,
1472 &lhs_ty,
1473 lhs.span(),
1474 &rhs_ty,
1475 rhs.span(),
1476 ));
1477 Some(PrimitiveType::Boolean.into())
1478 }
1479
1480 fn evaluate_numeric_expr<N: TreeNode + Exceptable>(
1482 &mut self,
1483 op: NumericOperator,
1484 span: Span,
1485 lhs: &Expr<N>,
1486 rhs: &Expr<N>,
1487 ) -> Option<Type> {
1488 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1489 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1490
1491 if lhs_ty.eq(&PrimitiveType::Integer.into()) && rhs_ty.eq(&PrimitiveType::Integer.into()) {
1493 return Some(PrimitiveType::Integer.into());
1494 }
1495
1496 if !lhs_ty.is_union()
1498 && lhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1499 && !rhs_ty.is_union()
1500 && rhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1501 {
1502 return Some(PrimitiveType::Float.into());
1503 }
1504
1505 if op == NumericOperator::Addition {
1509 let allow_optional = self.placeholders > 0;
1510 let other = if (!lhs_ty.is_optional() || allow_optional)
1511 && lhs_ty
1512 .as_primitive()
1513 .map(|p| p == PrimitiveType::String)
1514 .unwrap_or(false)
1515 {
1516 Some((lhs_ty.is_optional(), &rhs_ty, rhs.span()))
1517 } else if (!rhs_ty.is_optional() || allow_optional)
1518 && rhs_ty
1519 .as_primitive()
1520 .map(|p| p == PrimitiveType::String)
1521 .unwrap_or(false)
1522 {
1523 Some((rhs_ty.is_optional(), &lhs_ty, lhs.span()))
1524 } else {
1525 None
1526 };
1527
1528 if let Some((optional, other, span)) = other {
1529 if (!other.is_optional() || allow_optional)
1530 && other
1531 .as_primitive()
1532 .map(|p| p != PrimitiveType::Boolean)
1533 .unwrap_or(other.is_union() || (allow_optional && other.is_none()))
1534 {
1535 let ty: Type = PrimitiveType::String.into();
1536 if optional || other.is_optional() {
1537 return Some(ty.optional());
1538 }
1539
1540 return Some(ty);
1541 }
1542
1543 self.context
1544 .add_diagnostic(string_concat_mismatch(other, span));
1545 return None;
1546 }
1547 }
1548
1549 if !lhs_ty.is_union() && !rhs_ty.is_union() {
1550 self.context.add_diagnostic(numeric_mismatch(
1551 op,
1552 span,
1553 &lhs_ty,
1554 lhs.span(),
1555 &rhs_ty,
1556 rhs.span(),
1557 ));
1558 }
1559
1560 None
1561 }
1562
1563 fn evaluate_call_expr<N: TreeNode + Exceptable>(&mut self, expr: &CallExpr<N>) -> Option<Type> {
1565 let target = expr.target();
1566 let Some(f) = STDLIB.function(target.text()) else {
1567 self.context
1568 .add_diagnostic(unknown_function(target.text(), target.span()));
1569 return None;
1570 };
1571
1572 let mut count = 0;
1574 let mut arguments = [const { Type::Union }; MAX_PARAMETERS];
1575
1576 for arg in expr.arguments() {
1577 if count < MAX_PARAMETERS {
1578 arguments[count] = self.evaluate_expr(&arg).unwrap_or(Type::Union);
1579 }
1580
1581 count += 1;
1582 }
1583
1584 match target.text() {
1585 "find" | "matches" | "sub" => {
1586 if let Some(Expr::Literal(LiteralExpr::String(pattern_literal))) =
1588 expr.arguments().nth(1)
1589 && let Some(value) = pattern_literal.text()
1590 {
1591 let pattern = value.text().to_string();
1592 if let Err(e) = regex::Regex::new(&pattern) {
1593 self.context.add_diagnostic(invalid_regex_pattern(
1594 target.text(),
1595 value.text(),
1596 &e,
1597 pattern_literal.span(),
1598 ));
1599 }
1600 }
1601 }
1602 _ => {}
1603 }
1604
1605 let arguments = &arguments[..count.min(MAX_PARAMETERS)];
1606 if count <= MAX_PARAMETERS {
1607 match f.bind(self.context.version(), arguments) {
1608 Ok(binding) => {
1609 if let Some(severity) =
1610 self.context.diagnostics_config().unnecessary_function_call
1611 {
1612 self.check_unnecessary_call(expr, arguments, severity);
1613 }
1614 return Some(binding.return_type().clone());
1615 }
1616 Err(FunctionBindError::RequiresVersion(minimum)) => {
1617 self.context.add_diagnostic(unsupported_function(
1618 minimum,
1619 target.text(),
1620 target.span(),
1621 ));
1622 }
1623 Err(FunctionBindError::TooFewArguments(minimum)) => {
1624 self.context.add_diagnostic(too_few_arguments(
1625 target.text(),
1626 target.span(),
1627 minimum,
1628 count,
1629 ));
1630 }
1631 Err(FunctionBindError::TooManyArguments(maximum)) => {
1632 self.context.add_diagnostic(too_many_arguments(
1633 target.text(),
1634 target.span(),
1635 maximum,
1636 count,
1637 expr.arguments().skip(maximum).map(|e| e.span()),
1638 ));
1639 }
1640 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1641 self.context.add_diagnostic(argument_type_mismatch(
1642 target.text(),
1643 &expected,
1644 &arguments[index],
1645 expr.arguments()
1646 .nth(index)
1647 .map(|e| e.span())
1648 .expect("should have span"),
1649 ));
1650 }
1651 Err(FunctionBindError::Ambiguous { first, second }) => {
1652 self.context.add_diagnostic(ambiguous_argument(
1653 target.text(),
1654 target.span(),
1655 &first,
1656 &second,
1657 ));
1658 }
1659 }
1660 } else {
1661 match f.param_min_max(self.context.version()) {
1663 Some((_, max)) => {
1664 assert!(max <= MAX_PARAMETERS);
1665 self.context.add_diagnostic(too_many_arguments(
1666 target.text(),
1667 target.span(),
1668 max,
1669 count,
1670 expr.arguments().skip(max).map(|e| e.span()),
1671 ));
1672 }
1673 None => {
1674 self.context.add_diagnostic(unsupported_function(
1675 f.minimum_version(),
1676 target.text(),
1677 target.span(),
1678 ));
1679 }
1680 }
1681 }
1682
1683 Some(f.realize_unconstrained_return_type(arguments))
1684 }
1685
1686 fn evaluate_index_expr<N: TreeNode + Exceptable>(
1688 &mut self,
1689 expr: &IndexExpr<N>,
1690 ) -> Option<Type> {
1691 let (target, index) = expr.operands();
1692
1693 let target_ty = self.evaluate_expr(&target)?;
1695 let (expected_index_ty, result_ty) = match &target_ty {
1696 Type::Compound(CompoundType::Array(ty), _) => (
1697 Some(PrimitiveType::Integer.into()),
1698 Some(ty.element_type().clone()),
1699 ),
1700 Type::Compound(CompoundType::Map(ty), _) => {
1701 (Some(ty.key_type().clone()), Some(ty.value_type().clone()))
1702 }
1703 _ => (None, None),
1704 };
1705
1706 if let Some(expected_index_ty) = expected_index_ty {
1708 let index_ty = self.evaluate_expr(&index).unwrap_or(Type::Union);
1709 if !index_ty.is_coercible_to(&expected_index_ty) {
1710 self.context.add_diagnostic(index_type_mismatch(
1711 &expected_index_ty,
1712 &index_ty,
1713 index.span(),
1714 ));
1715 }
1716 }
1717
1718 match result_ty {
1719 Some(ty) => Some(ty),
1720 None => {
1721 self.context
1722 .add_diagnostic(cannot_index(&target_ty, target.span()));
1723 None
1724 }
1725 }
1726 }
1727
1728 fn evaluate_access_expr<N: TreeNode + Exceptable>(
1730 &mut self,
1731 expr: &AccessExpr<N>,
1732 ) -> Option<Type> {
1733 let (target, name) = expr.operands();
1734 let ty = self.evaluate_expr(&target)?;
1735
1736 match &ty {
1737 Type::Hidden(HiddenType::TaskPreEvaluation) => {
1738 return match task_member_type_pre_evaluation(name.text()) {
1739 Some(ty) => Some(ty),
1740 None => {
1741 self.context.add_diagnostic(not_a_task_member(&name));
1742 return None;
1743 }
1744 };
1745 }
1746 Type::Hidden(HiddenType::TaskPostEvaluation) => {
1747 return match task_member_type_post_evaluation(self.context.version(), name.text()) {
1748 Some(ty) => Some(ty),
1749 None => {
1750 self.context.add_diagnostic(not_a_task_member(&name));
1751 return None;
1752 }
1753 };
1754 }
1755 Type::Hidden(HiddenType::PreviousTaskData) => {
1756 return match previous_task_data_member_type(name.text()) {
1757 Some(ty) => Some(ty),
1758 None => {
1759 self.context
1760 .add_diagnostic(not_a_previous_task_data_member(&name));
1761 return None;
1762 }
1763 };
1764 }
1765 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => {
1766 if let Some(ty) = ty.members().get(name.text()) {
1767 return Some(ty.clone());
1768 }
1769
1770 self.context
1771 .add_diagnostic(not_a_struct_member(ty.name(), &name));
1772 return None;
1773 }
1774 Type::Compound(CompoundType::Pair(ty), _) => {
1775 return match name.text() {
1777 "left" => Some(ty.left_type().clone()),
1778 "right" => Some(ty.right_type().clone()),
1779 _ => {
1780 self.context.add_diagnostic(not_a_pair_accessor(&name));
1781 None
1782 }
1783 };
1784 }
1785 Type::Call(ty) => {
1786 if let Some(output) = ty.outputs().get(name.text()) {
1787 return Some(output.ty().clone());
1788 }
1789
1790 self.context
1791 .add_diagnostic(unknown_call_io(ty, &name, Io::Output));
1792 return None;
1793 }
1794 Type::TypeNameRef(custom_ty) => match custom_ty {
1795 CustomType::Struct(_) => {
1796 self.context
1797 .add_diagnostic(cannot_access(&ty, target.span()));
1798 return None;
1799 }
1800 CustomType::Enum(_) => {
1801 return Some(Type::from(CompoundType::Custom(custom_ty.clone())));
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}