1use std::fmt;
4use std::fmt::Write;
5use std::sync::Arc;
6use std::sync::LazyLock;
7
8use wdl_ast::AstNode;
9use wdl_ast::AstToken;
10use wdl_ast::Diagnostic;
11use wdl_ast::Ident;
12use wdl_ast::Severity;
13use wdl_ast::Span;
14use wdl_ast::SupportedVersion;
15use wdl_ast::TreeNode;
16use wdl_ast::TreeToken;
17use wdl_ast::v1;
18use wdl_ast::v1::AccessExpr;
19use wdl_ast::v1::CallExpr;
20use wdl_ast::v1::Expr;
21use wdl_ast::v1::IfExpr;
22use wdl_ast::v1::IndexExpr;
23use wdl_ast::v1::LiteralArray;
24use wdl_ast::v1::LiteralExpr;
25use wdl_ast::v1::LiteralHints;
26use wdl_ast::v1::LiteralInput;
27use wdl_ast::v1::LiteralMap;
28use wdl_ast::v1::LiteralMapItem;
29use wdl_ast::v1::LiteralObject;
30use wdl_ast::v1::LiteralOutput;
31use wdl_ast::v1::LiteralPair;
32use wdl_ast::v1::LiteralStruct;
33use wdl_ast::v1::LogicalAndExpr;
34use wdl_ast::v1::LogicalNotExpr;
35use wdl_ast::v1::LogicalOrExpr;
36use wdl_ast::v1::NegationExpr;
37use wdl_ast::v1::Placeholder;
38use wdl_ast::v1::PlaceholderOption;
39use wdl_ast::v1::StringPart;
40use wdl_ast::v1::TASK_FIELD_ATTEMPT;
41use wdl_ast::v1::TASK_FIELD_CONTAINER;
42use wdl_ast::v1::TASK_FIELD_CPU;
43use wdl_ast::v1::TASK_FIELD_DISKS;
44use wdl_ast::v1::TASK_FIELD_END_TIME;
45use wdl_ast::v1::TASK_FIELD_EXT;
46use wdl_ast::v1::TASK_FIELD_FPGA;
47use wdl_ast::v1::TASK_FIELD_GPU;
48use wdl_ast::v1::TASK_FIELD_ID;
49use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
50use wdl_ast::v1::TASK_FIELD_MEMORY;
51use wdl_ast::v1::TASK_FIELD_META;
52use wdl_ast::v1::TASK_FIELD_NAME;
53use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
54use wdl_ast::v1::TASK_FIELD_PREVIOUS;
55use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
56use wdl_ast::v1::TASK_HINT_CACHEABLE;
57use wdl_ast::v1::TASK_HINT_DISKS;
58use wdl_ast::v1::TASK_HINT_FPGA;
59use wdl_ast::v1::TASK_HINT_GPU;
60use wdl_ast::v1::TASK_HINT_INPUTS;
61use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL;
62use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS;
63use wdl_ast::v1::TASK_HINT_MAX_CPU;
64use wdl_ast::v1::TASK_HINT_MAX_CPU_ALIAS;
65use wdl_ast::v1::TASK_HINT_MAX_MEMORY;
66use wdl_ast::v1::TASK_HINT_MAX_MEMORY_ALIAS;
67use wdl_ast::v1::TASK_HINT_OUTPUTS;
68use wdl_ast::v1::TASK_HINT_SHORT_TASK;
69use wdl_ast::v1::TASK_HINT_SHORT_TASK_ALIAS;
70use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER;
71use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER_ALIAS;
72use wdl_ast::v1::TASK_REQUIREMENT_CPU;
73use wdl_ast::v1::TASK_REQUIREMENT_DISKS;
74use wdl_ast::v1::TASK_REQUIREMENT_FPGA;
75use wdl_ast::v1::TASK_REQUIREMENT_GPU;
76use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES;
77use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES_ALIAS;
78use wdl_ast::v1::TASK_REQUIREMENT_MEMORY;
79use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES;
80use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES_ALIAS;
81use wdl_ast::version::V1;
82
83use super::ArrayType;
84use super::CompoundType;
85use super::HiddenType;
86use super::MapType;
87use super::Optional;
88use super::PairType;
89use super::PrimitiveType;
90use super::StructType;
91use super::Type;
92use super::TypeNameResolver;
93use crate::Exceptable;
94use crate::UNNECESSARY_FUNCTION_CALL;
95use crate::config::DiagnosticsConfig;
96use crate::diagnostics::Io;
97use crate::diagnostics::ambiguous_argument;
98use crate::diagnostics::argument_type_mismatch;
99use crate::diagnostics::cannot_access;
100use crate::diagnostics::cannot_coerce_to_string;
101use crate::diagnostics::cannot_index;
102use crate::diagnostics::comparison_mismatch;
103use crate::diagnostics::if_conditional_mismatch;
104use crate::diagnostics::index_type_mismatch;
105use crate::diagnostics::invalid_placeholder_option;
106use crate::diagnostics::invalid_regex_pattern;
107use crate::diagnostics::logical_and_mismatch;
108use crate::diagnostics::logical_not_mismatch;
109use crate::diagnostics::logical_or_mismatch;
110use crate::diagnostics::map_key_not_primitive;
111use crate::diagnostics::missing_struct_members;
112use crate::diagnostics::multiple_type_mismatch;
113use crate::diagnostics::negation_mismatch;
114use crate::diagnostics::no_common_type;
115use crate::diagnostics::not_a_pair_accessor;
116use crate::diagnostics::not_a_previous_task_data_member;
117use crate::diagnostics::not_a_struct;
118use crate::diagnostics::not_a_struct_member;
119use crate::diagnostics::not_a_task_member;
120use crate::diagnostics::numeric_mismatch;
121use crate::diagnostics::string_concat_mismatch;
122use crate::diagnostics::too_few_arguments;
123use crate::diagnostics::too_many_arguments;
124use crate::diagnostics::type_mismatch;
125use crate::diagnostics::unknown_call_io;
126use crate::diagnostics::unknown_function;
127use crate::diagnostics::unknown_task_io;
128use crate::diagnostics::unnecessary_function_call;
129use crate::diagnostics::unsupported_function;
130use crate::document::Task;
131use crate::stdlib::FunctionBindError;
132use crate::stdlib::MAX_PARAMETERS;
133use crate::stdlib::STDLIB;
134use crate::types::Coercible;
135use crate::types::CustomType;
136
137pub fn task_member_type_pre_evaluation(name: &str) -> Option<Type> {
144 match name {
145 TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
146 TASK_FIELD_ATTEMPT => Some(PrimitiveType::Integer.into()),
147 TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
148 TASK_FIELD_PREVIOUS => Some(Type::Hidden(HiddenType::PreviousTaskData)),
149 _ => None,
150 }
151}
152
153pub fn task_member_type_post_evaluation(version: SupportedVersion, name: &str) -> Option<Type> {
160 match name {
161 TASK_FIELD_NAME | TASK_FIELD_ID => Some(PrimitiveType::String.into()),
162 TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
163 TASK_FIELD_CPU => Some(PrimitiveType::Float.into()),
164 TASK_FIELD_MEMORY | TASK_FIELD_ATTEMPT => Some(PrimitiveType::Integer.into()),
165 TASK_FIELD_GPU | TASK_FIELD_FPGA => Some(STDLIB.array_string_type().clone().into()),
166 TASK_FIELD_DISKS => Some(STDLIB.map_string_int_type().clone().into()),
167 TASK_FIELD_END_TIME | TASK_FIELD_RETURN_CODE => {
168 Some(Type::from(PrimitiveType::Integer).optional())
169 }
170 TASK_FIELD_META | TASK_FIELD_PARAMETER_META | TASK_FIELD_EXT => Some(Type::Object),
171 TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
172 Some(PrimitiveType::Integer.into())
173 }
174 TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
175 Some(Type::Hidden(HiddenType::PreviousTaskData))
176 }
177 _ => None,
178 }
179}
180
181pub fn previous_task_data_member_type(name: &str) -> Option<Type> {
185 match name {
186 TASK_FIELD_MEMORY => Some(Type::from(PrimitiveType::Integer).optional()),
187 TASK_FIELD_CPU => Some(Type::from(PrimitiveType::Float).optional()),
188 TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
189 TASK_FIELD_GPU | TASK_FIELD_FPGA => {
190 Some(Type::from(STDLIB.array_string_type().clone()).optional())
191 }
192 TASK_FIELD_DISKS => Some(Type::from(STDLIB.map_string_int_type().clone()).optional()),
193 TASK_FIELD_MAX_RETRIES => Some(Type::from(PrimitiveType::Integer).optional()),
194 _ => None,
195 }
196}
197
198pub fn task_requirement_types(version: SupportedVersion, name: &str) -> Option<&'static [Type]> {
202 static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
204 Box::new([
205 PrimitiveType::String.into(),
206 STDLIB.array_string_type().clone().into(),
207 ])
208 });
209 const CPU_TYPES: &[Type] = &[
211 Type::Primitive(PrimitiveType::Integer, false),
212 Type::Primitive(PrimitiveType::Float, false),
213 ];
214 const MEMORY_TYPES: &[Type] = &[
216 Type::Primitive(PrimitiveType::Integer, false),
217 Type::Primitive(PrimitiveType::String, false),
218 ];
219 const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
221 const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
223 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
225 Box::new([
226 PrimitiveType::Integer.into(),
227 PrimitiveType::String.into(),
228 STDLIB.array_string_type().clone().into(),
229 ])
230 });
231 const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
233 static RETURN_CODES_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
235 Box::new([
236 PrimitiveType::Integer.into(),
237 PrimitiveType::String.into(),
238 STDLIB.array_int_type().clone().into(),
239 ])
240 });
241
242 match name {
243 TASK_REQUIREMENT_CONTAINER | TASK_REQUIREMENT_CONTAINER_ALIAS => Some(&CONTAINER_TYPES),
244 TASK_REQUIREMENT_CPU => Some(CPU_TYPES),
245 TASK_REQUIREMENT_DISKS => Some(&DISKS_TYPES),
246 TASK_REQUIREMENT_GPU => Some(GPU_TYPES),
247 TASK_REQUIREMENT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
248 TASK_REQUIREMENT_MAX_RETRIES if version >= SupportedVersion::V1(V1::Two) => {
249 Some(MAX_RETRIES_TYPES)
250 }
251 TASK_REQUIREMENT_MAX_RETRIES_ALIAS => Some(MAX_RETRIES_TYPES),
252 TASK_REQUIREMENT_MEMORY => Some(MEMORY_TYPES),
253 TASK_REQUIREMENT_RETURN_CODES if version >= SupportedVersion::V1(V1::Two) => {
254 Some(&RETURN_CODES_TYPES)
255 }
256 TASK_REQUIREMENT_RETURN_CODES_ALIAS => Some(&RETURN_CODES_TYPES),
257 _ => None,
258 }
259}
260
261pub fn task_hint_types(
265 version: SupportedVersion,
266 name: &str,
267 use_hidden_types: bool,
268) -> Option<&'static [Type]> {
269 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
271 Box::new([
272 PrimitiveType::String.into(),
273 STDLIB.map_string_string_type().clone().into(),
274 ])
275 });
276 const FPGA_TYPES: &[Type] = &[
278 Type::Primitive(PrimitiveType::Integer, false),
279 Type::Primitive(PrimitiveType::String, false),
280 ];
281 const GPU_TYPES: &[Type] = &[
283 Type::Primitive(PrimitiveType::Integer, false),
284 Type::Primitive(PrimitiveType::String, false),
285 ];
286 const INPUTS_TYPES: &[Type] = &[Type::Object];
288 const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Input)];
290 const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
292 const MAX_CPU_TYPES: &[Type] = &[
294 Type::Primitive(PrimitiveType::Integer, false),
295 Type::Primitive(PrimitiveType::Float, false),
296 ];
297 const MAX_MEMORY_TYPES: &[Type] = &[
299 Type::Primitive(PrimitiveType::Integer, false),
300 Type::Primitive(PrimitiveType::String, false),
301 ];
302 const OUTPUTS_TYPES: &[Type] = &[Type::Object];
304 const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Hidden(HiddenType::Output)];
306 const SHORT_TASK_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
308 const CACHEABLE_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
310
311 match name {
312 TASK_HINT_DISKS => Some(&DISKS_TYPES),
313 TASK_HINT_FPGA if version >= SupportedVersion::V1(V1::Two) => Some(FPGA_TYPES),
314 TASK_HINT_GPU => Some(GPU_TYPES),
315 TASK_HINT_INPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
316 Some(INPUTS_HIDDEN_TYPES)
317 }
318 TASK_HINT_INPUTS => Some(INPUTS_TYPES),
319 TASK_HINT_LOCALIZATION_OPTIONAL if version >= SupportedVersion::V1(V1::Two) => {
320 Some(LOCALIZATION_OPTIONAL_TYPES)
321 }
322 TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS => Some(LOCALIZATION_OPTIONAL_TYPES),
323 TASK_HINT_MAX_CPU if version >= SupportedVersion::V1(V1::Two) => Some(MAX_CPU_TYPES),
324 TASK_HINT_MAX_CPU_ALIAS => Some(MAX_CPU_TYPES),
325 TASK_HINT_MAX_MEMORY if version >= SupportedVersion::V1(V1::Two) => Some(MAX_MEMORY_TYPES),
326 TASK_HINT_MAX_MEMORY_ALIAS => Some(MAX_MEMORY_TYPES),
327 TASK_HINT_OUTPUTS if use_hidden_types && version >= SupportedVersion::V1(V1::Two) => {
328 Some(OUTPUTS_HIDDEN_TYPES)
329 }
330 TASK_HINT_OUTPUTS => Some(OUTPUTS_TYPES),
331 TASK_HINT_SHORT_TASK if version >= SupportedVersion::V1(V1::Two) => Some(SHORT_TASK_TYPES),
332 TASK_HINT_SHORT_TASK_ALIAS => Some(SHORT_TASK_TYPES),
333 TASK_HINT_CACHEABLE => Some(CACHEABLE_TYPES),
334 _ => None,
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum ComparisonOperator {
341 Equality,
343 Inequality,
345 Less,
347 LessEqual,
349 Greater,
351 GreaterEqual,
353}
354
355impl fmt::Display for ComparisonOperator {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 write!(
358 f,
359 "{}",
360 match self {
361 Self::Equality => "==",
362 Self::Inequality => "!=",
363 Self::Less => "<",
364 Self::LessEqual => "<=",
365 Self::Greater => ">",
366 Self::GreaterEqual => ">=",
367 }
368 )
369 }
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub enum NumericOperator {
375 Addition,
377 Subtraction,
379 Multiplication,
381 Division,
383 Modulo,
385 Exponentiation,
387}
388
389impl fmt::Display for NumericOperator {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(
392 f,
393 "{}",
394 match self {
395 Self::Addition => "addition",
396 Self::Subtraction => "subtraction",
397 Self::Multiplication => "multiplication",
398 Self::Division => "division",
399 Self::Modulo => "remainder",
400 Self::Exponentiation => "exponentiation",
401 }
402 )
403 }
404}
405
406#[derive(Debug)]
408pub struct AstTypeConverter<R>(R);
409
410impl<R> AstTypeConverter<R>
411where
412 R: TypeNameResolver,
413{
414 pub fn new(resolver: R) -> Self {
416 Self(resolver)
417 }
418
419 pub fn convert_type<N: TreeNode>(&mut self, ty: &v1::Type<N>) -> Result<Type, Diagnostic> {
424 let optional = ty.is_optional();
425
426 let ty: Type = match ty {
427 v1::Type::Map(ty) => {
428 let ty = self.convert_map_type(ty)?;
429 ty.into()
430 }
431 v1::Type::Array(ty) => {
432 let ty = self.convert_array_type(ty)?;
433 ty.into()
434 }
435 v1::Type::Pair(ty) => {
436 let ty = self.convert_pair_type(ty)?;
437 ty.into()
438 }
439 v1::Type::Object(_) => Type::Object,
440 v1::Type::Ref(r) => {
441 let name = r.name();
442 self.0.resolve(name.text(), name.span())?
443 }
444 v1::Type::Primitive(ty) => Type::Primitive(ty.kind().into(), false),
445 };
446
447 if optional { Ok(ty.optional()) } else { Ok(ty) }
448 }
449
450 pub fn convert_array_type<N: TreeNode>(
455 &mut self,
456 ty: &v1::ArrayType<N>,
457 ) -> Result<ArrayType, Diagnostic> {
458 let element_type = self.convert_type(&ty.element_type())?;
459 if ty.is_non_empty() {
460 Ok(ArrayType::non_empty(element_type))
461 } else {
462 Ok(ArrayType::new(element_type))
463 }
464 }
465
466 pub fn convert_pair_type<N: TreeNode>(
471 &mut self,
472 ty: &v1::PairType<N>,
473 ) -> Result<PairType, Diagnostic> {
474 let (left_type, right_type) = ty.types();
475 Ok(PairType::new(
476 self.convert_type(&left_type)?,
477 self.convert_type(&right_type)?,
478 ))
479 }
480
481 pub fn convert_map_type<N: TreeNode>(
486 &mut self,
487 ty: &v1::MapType<N>,
488 ) -> Result<MapType, Diagnostic> {
489 let (key_type, value_type) = ty.types();
490 let key_type =
491 Type::Primitive(PrimitiveType::from(key_type.kind()), key_type.is_optional());
492
493 if key_type.is_optional() {
495 return Err(map_key_not_primitive(ty.types().0.span(), &key_type));
496 }
497
498 Ok(MapType::new(key_type, self.convert_type(&value_type)?))
499 }
500
501 pub fn convert_struct_type<N: TreeNode>(
506 &mut self,
507 definition: &v1::StructDefinition<N>,
508 ) -> Result<StructType, Diagnostic> {
509 Ok(StructType {
510 name: Arc::new(definition.name().text().to_string()),
511 members: Arc::new(
512 definition
513 .members()
514 .map(|d| Ok((d.name().text().to_string(), self.convert_type(&d.ty())?)))
515 .collect::<Result<_, _>>()?,
516 ),
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, ty) = &ty.members.get_index(i).unwrap();
957 if 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)),
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.inner().is_rule_excepted(UNNECESSARY_FUNCTION_CALL)
1568 {
1569 self.check_unnecessary_call(
1570 &target,
1571 arguments,
1572 expr.arguments().map(|e| e.span()),
1573 severity,
1574 );
1575 }
1576 return Some(binding.return_type().clone());
1577 }
1578 Err(FunctionBindError::RequiresVersion(minimum)) => {
1579 self.context.add_diagnostic(unsupported_function(
1580 minimum,
1581 target.text(),
1582 target.span(),
1583 ));
1584 }
1585 Err(FunctionBindError::TooFewArguments(minimum)) => {
1586 self.context.add_diagnostic(too_few_arguments(
1587 target.text(),
1588 target.span(),
1589 minimum,
1590 count,
1591 ));
1592 }
1593 Err(FunctionBindError::TooManyArguments(maximum)) => {
1594 self.context.add_diagnostic(too_many_arguments(
1595 target.text(),
1596 target.span(),
1597 maximum,
1598 count,
1599 expr.arguments().skip(maximum).map(|e| e.span()),
1600 ));
1601 }
1602 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1603 self.context.add_diagnostic(argument_type_mismatch(
1604 target.text(),
1605 &expected,
1606 &arguments[index],
1607 expr.arguments()
1608 .nth(index)
1609 .map(|e| e.span())
1610 .expect("should have span"),
1611 ));
1612 }
1613 Err(FunctionBindError::Ambiguous { first, second }) => {
1614 self.context.add_diagnostic(ambiguous_argument(
1615 target.text(),
1616 target.span(),
1617 &first,
1618 &second,
1619 ));
1620 }
1621 }
1622 } else {
1623 match f.param_min_max(self.context.version()) {
1625 Some((_, max)) => {
1626 assert!(max <= MAX_PARAMETERS);
1627 self.context.add_diagnostic(too_many_arguments(
1628 target.text(),
1629 target.span(),
1630 max,
1631 count,
1632 expr.arguments().skip(max).map(|e| e.span()),
1633 ));
1634 }
1635 None => {
1636 self.context.add_diagnostic(unsupported_function(
1637 f.minimum_version(),
1638 target.text(),
1639 target.span(),
1640 ));
1641 }
1642 }
1643 }
1644
1645 Some(f.realize_unconstrained_return_type(arguments))
1646 }
1647 None => {
1648 self.context
1649 .add_diagnostic(unknown_function(target.text(), target.span()));
1650 None
1651 }
1652 }
1653 }
1654
1655 fn evaluate_index_expr<N: TreeNode + Exceptable>(
1657 &mut self,
1658 expr: &IndexExpr<N>,
1659 ) -> Option<Type> {
1660 let (target, index) = expr.operands();
1661
1662 let target_ty = self.evaluate_expr(&target)?;
1664 let (expected_index_ty, result_ty) = match &target_ty {
1665 Type::Compound(CompoundType::Array(ty), _) => (
1666 Some(PrimitiveType::Integer.into()),
1667 Some(ty.element_type().clone()),
1668 ),
1669 Type::Compound(CompoundType::Map(ty), _) => {
1670 (Some(ty.key_type().clone()), Some(ty.value_type().clone()))
1671 }
1672 _ => (None, None),
1673 };
1674
1675 if let Some(expected_index_ty) = expected_index_ty {
1677 let index_ty = self.evaluate_expr(&index).unwrap_or(Type::Union);
1678 if !index_ty.is_coercible_to(&expected_index_ty) {
1679 self.context.add_diagnostic(index_type_mismatch(
1680 &expected_index_ty,
1681 &index_ty,
1682 index.span(),
1683 ));
1684 }
1685 }
1686
1687 match result_ty {
1688 Some(ty) => Some(ty),
1689 None => {
1690 self.context
1691 .add_diagnostic(cannot_index(&target_ty, target.span()));
1692 None
1693 }
1694 }
1695 }
1696
1697 fn evaluate_access_expr<N: TreeNode + Exceptable>(
1699 &mut self,
1700 expr: &AccessExpr<N>,
1701 ) -> Option<Type> {
1702 let (target, name) = expr.operands();
1703 let ty = self.evaluate_expr(&target)?;
1704
1705 match &ty {
1706 Type::Hidden(HiddenType::TaskPreEvaluation) => {
1707 return match task_member_type_pre_evaluation(name.text()) {
1708 Some(ty) => Some(ty),
1709 None => {
1710 self.context.add_diagnostic(not_a_task_member(&name));
1711 return None;
1712 }
1713 };
1714 }
1715 Type::Hidden(HiddenType::TaskPostEvaluation) => {
1716 return match task_member_type_post_evaluation(self.context.version(), name.text()) {
1717 Some(ty) => Some(ty),
1718 None => {
1719 self.context.add_diagnostic(not_a_task_member(&name));
1720 return None;
1721 }
1722 };
1723 }
1724 Type::Hidden(HiddenType::PreviousTaskData) => {
1725 return match previous_task_data_member_type(name.text()) {
1726 Some(ty) => Some(ty),
1727 None => {
1728 self.context
1729 .add_diagnostic(not_a_previous_task_data_member(&name));
1730 return None;
1731 }
1732 };
1733 }
1734 Type::Compound(CompoundType::Custom(CustomType::Struct(ty)), _) => {
1735 if let Some(ty) = ty.members.get(name.text()) {
1736 return Some(ty.clone());
1737 }
1738
1739 self.context
1740 .add_diagnostic(not_a_struct_member(ty.name(), &name));
1741 return None;
1742 }
1743 Type::Compound(CompoundType::Pair(ty), _) => {
1744 return match name.text() {
1746 "left" => Some(ty.left_type().clone()),
1747 "right" => Some(ty.right_type().clone()),
1748 _ => {
1749 self.context.add_diagnostic(not_a_pair_accessor(&name));
1750 None
1751 }
1752 };
1753 }
1754 Type::Call(ty) => {
1755 if let Some(output) = ty.outputs().get(name.text()) {
1756 return Some(output.ty().clone());
1757 }
1758
1759 self.context
1760 .add_diagnostic(unknown_call_io(ty, &name, Io::Output));
1761 return None;
1762 }
1763 Type::TypeNameRef(custom_ty) => match custom_ty {
1764 CustomType::Struct(_) => {
1765 self.context
1766 .add_diagnostic(cannot_access(&ty, target.span()));
1767 return None;
1768 }
1769 CustomType::Enum(_) => {
1770 return Some(Type::from(CompoundType::Custom(custom_ty.clone())));
1771 }
1772 },
1773 _ => {}
1774 }
1775
1776 if ty.is_coercible_to(&Type::OptionalObject) {
1779 return Some(Type::Union);
1780 }
1781
1782 self.context
1783 .add_diagnostic(cannot_access(&ty, target.span()));
1784 None
1785 }
1786
1787 fn check_unnecessary_call<T: TreeToken>(
1789 &mut self,
1790 target: &Ident<T>,
1791 arguments: &[Type],
1792 mut spans: impl Iterator<Item = Span>,
1793 severity: Severity,
1794 ) {
1795 let (label, span, fix) = match target.text() {
1796 "select_first" => {
1797 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1798 if ty.is_optional() || ty.is_union() {
1799 return;
1800 }
1801 (
1802 format!("array element type `{ty}` is not optional"),
1803 spans.next().expect("should have span"),
1804 "replace the function call with the array's first element",
1805 )
1806 } else {
1807 return;
1808 }
1809 }
1810 "select_all" => {
1811 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1812 if ty.is_optional() || ty.is_union() {
1813 return;
1814 }
1815 (
1816 format!("array element type `{ty}` is not optional"),
1817 spans.next().expect("should have span"),
1818 "replace the function call with the array itself",
1819 )
1820 } else {
1821 return;
1822 }
1823 }
1824 "defined" => {
1825 if arguments[0].is_optional() || arguments[0].is_union() {
1826 return;
1827 }
1828
1829 (
1830 format!("type `{ty}` is not optional", ty = arguments[0]),
1831 spans.next().expect("should have span"),
1832 "replace the function call with `true`",
1833 )
1834 }
1835 _ => return,
1836 };
1837
1838 self.context.add_diagnostic(
1839 unnecessary_function_call(target.text(), target.span(), &label, span)
1840 .with_severity(severity)
1841 .with_fix(fix),
1842 )
1843 }
1844}