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