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