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