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