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_MEMORY;
50use wdl_ast::v1::TASK_FIELD_META;
51use wdl_ast::v1::TASK_FIELD_NAME;
52use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
53use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
54use wdl_ast::v1::TASK_HINT_DISKS;
55use wdl_ast::v1::TASK_HINT_FPGA;
56use wdl_ast::v1::TASK_HINT_GPU;
57use wdl_ast::v1::TASK_HINT_INPUTS;
58use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL;
59use wdl_ast::v1::TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS;
60use wdl_ast::v1::TASK_HINT_MAX_CPU;
61use wdl_ast::v1::TASK_HINT_MAX_CPU_ALIAS;
62use wdl_ast::v1::TASK_HINT_MAX_MEMORY;
63use wdl_ast::v1::TASK_HINT_MAX_MEMORY_ALIAS;
64use wdl_ast::v1::TASK_HINT_OUTPUTS;
65use wdl_ast::v1::TASK_HINT_SHORT_TASK;
66use wdl_ast::v1::TASK_HINT_SHORT_TASK_ALIAS;
67use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER;
68use wdl_ast::v1::TASK_REQUIREMENT_CONTAINER_ALIAS;
69use wdl_ast::v1::TASK_REQUIREMENT_CPU;
70use wdl_ast::v1::TASK_REQUIREMENT_DISKS;
71use wdl_ast::v1::TASK_REQUIREMENT_FPGA;
72use wdl_ast::v1::TASK_REQUIREMENT_GPU;
73use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES;
74use wdl_ast::v1::TASK_REQUIREMENT_MAX_RETRIES_ALIAS;
75use wdl_ast::v1::TASK_REQUIREMENT_MEMORY;
76use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES;
77use wdl_ast::v1::TASK_REQUIREMENT_RETURN_CODES_ALIAS;
78use wdl_ast::version::V1;
79
80use super::ArrayType;
81use super::CompoundType;
82use super::MapType;
83use super::Optional;
84use super::PairType;
85use super::PrimitiveType;
86use super::StructType;
87use super::Type;
88use super::TypeNameResolver;
89use crate::SyntaxNodeExt;
90use crate::UNNECESSARY_FUNCTION_CALL;
91use crate::config::DiagnosticsConfig;
92use crate::diagnostics::Io;
93use crate::diagnostics::ambiguous_argument;
94use crate::diagnostics::argument_type_mismatch;
95use crate::diagnostics::cannot_access;
96use crate::diagnostics::cannot_coerce_to_string;
97use crate::diagnostics::cannot_index;
98use crate::diagnostics::comparison_mismatch;
99use crate::diagnostics::if_conditional_mismatch;
100use crate::diagnostics::index_type_mismatch;
101use crate::diagnostics::invalid_placeholder_option;
102use crate::diagnostics::invalid_regex_pattern;
103use crate::diagnostics::logical_and_mismatch;
104use crate::diagnostics::logical_not_mismatch;
105use crate::diagnostics::logical_or_mismatch;
106use crate::diagnostics::map_key_not_primitive;
107use crate::diagnostics::missing_struct_members;
108use crate::diagnostics::multiple_type_mismatch;
109use crate::diagnostics::negation_mismatch;
110use crate::diagnostics::no_common_type;
111use crate::diagnostics::not_a_pair_accessor;
112use crate::diagnostics::not_a_struct;
113use crate::diagnostics::not_a_struct_member;
114use crate::diagnostics::not_a_task_member;
115use crate::diagnostics::numeric_mismatch;
116use crate::diagnostics::string_concat_mismatch;
117use crate::diagnostics::too_few_arguments;
118use crate::diagnostics::too_many_arguments;
119use crate::diagnostics::type_mismatch;
120use crate::diagnostics::unknown_call_io;
121use crate::diagnostics::unknown_function;
122use crate::diagnostics::unknown_task_io;
123use crate::diagnostics::unnecessary_function_call;
124use crate::diagnostics::unsupported_function;
125use crate::document::Task;
126use crate::stdlib::FunctionBindError;
127use crate::stdlib::MAX_PARAMETERS;
128use crate::stdlib::STDLIB;
129use crate::types::Coercible;
130pub fn task_member_type(name: &str) -> Option<Type> {
136 match name {
137 n if n == TASK_FIELD_NAME || n == TASK_FIELD_ID => Some(PrimitiveType::String.into()),
138 n if n == TASK_FIELD_CONTAINER => Some(Type::from(PrimitiveType::String).optional()),
139 n if n == TASK_FIELD_CPU => Some(PrimitiveType::Float.into()),
140 n if n == TASK_FIELD_MEMORY || n == TASK_FIELD_ATTEMPT => {
141 Some(PrimitiveType::Integer.into())
142 }
143 n if n == TASK_FIELD_GPU || n == TASK_FIELD_FPGA => {
144 Some(STDLIB.array_string_type().clone())
145 }
146 n if n == TASK_FIELD_DISKS => Some(STDLIB.map_string_int_type().clone()),
147 n if n == TASK_FIELD_END_TIME || n == TASK_FIELD_RETURN_CODE => {
148 Some(Type::from(PrimitiveType::Integer).optional())
149 }
150 n if n == TASK_FIELD_META || n == TASK_FIELD_PARAMETER_META || n == TASK_FIELD_EXT => {
151 Some(Type::Object)
152 }
153 _ => None,
154 }
155}
156
157pub fn task_requirement_types(version: SupportedVersion, name: &str) -> Option<&'static [Type]> {
161 static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
163 Box::new([
164 PrimitiveType::String.into(),
165 STDLIB.array_string_type().clone(),
166 ])
167 });
168 const CPU_TYPES: &[Type] = &[
170 Type::Primitive(PrimitiveType::Integer, false),
171 Type::Primitive(PrimitiveType::Float, false),
172 ];
173 const MEMORY_TYPES: &[Type] = &[
175 Type::Primitive(PrimitiveType::Integer, false),
176 Type::Primitive(PrimitiveType::String, false),
177 ];
178 const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
180 const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
182 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
184 Box::new([
185 PrimitiveType::Integer.into(),
186 PrimitiveType::String.into(),
187 STDLIB.array_string_type().clone(),
188 ])
189 });
190 const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
192 static RETURN_CODES_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
194 Box::new([
195 PrimitiveType::Integer.into(),
196 PrimitiveType::String.into(),
197 STDLIB.array_int_type().clone(),
198 ])
199 });
200
201 match name {
202 n if n == TASK_REQUIREMENT_CONTAINER || n == TASK_REQUIREMENT_CONTAINER_ALIAS => {
203 Some(&CONTAINER_TYPES)
204 }
205 n if n == TASK_REQUIREMENT_CPU => Some(CPU_TYPES),
206 n if n == TASK_REQUIREMENT_DISKS => Some(&DISKS_TYPES),
207 n if n == TASK_REQUIREMENT_GPU => Some(GPU_TYPES),
208 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_REQUIREMENT_FPGA => {
209 Some(FPGA_TYPES)
210 }
211 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_REQUIREMENT_MAX_RETRIES => {
212 Some(MAX_RETRIES_TYPES)
213 }
214 n if n == TASK_REQUIREMENT_MAX_RETRIES_ALIAS => Some(MAX_RETRIES_TYPES),
215 n if n == TASK_REQUIREMENT_MEMORY => Some(MEMORY_TYPES),
216 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_REQUIREMENT_RETURN_CODES => {
217 Some(&RETURN_CODES_TYPES)
218 }
219 n if n == TASK_REQUIREMENT_RETURN_CODES_ALIAS => Some(&RETURN_CODES_TYPES),
220 _ => None,
221 }
222}
223
224pub fn task_hint_types(
228 version: SupportedVersion,
229 name: &str,
230 use_hidden_types: bool,
231) -> Option<&'static [Type]> {
232 static DISKS_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
234 Box::new([
235 PrimitiveType::String.into(),
236 STDLIB.map_string_string_type().clone(),
237 ])
238 });
239 const FPGA_TYPES: &[Type] = &[
241 Type::Primitive(PrimitiveType::Integer, false),
242 Type::Primitive(PrimitiveType::String, false),
243 ];
244 const GPU_TYPES: &[Type] = &[
246 Type::Primitive(PrimitiveType::Integer, false),
247 Type::Primitive(PrimitiveType::String, false),
248 ];
249 const INPUTS_TYPES: &[Type] = &[Type::Object];
251 const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Input];
253 const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
255 const MAX_CPU_TYPES: &[Type] = &[
257 Type::Primitive(PrimitiveType::Integer, false),
258 Type::Primitive(PrimitiveType::Float, false),
259 ];
260 const MAX_MEMORY_TYPES: &[Type] = &[
262 Type::Primitive(PrimitiveType::Integer, false),
263 Type::Primitive(PrimitiveType::String, false),
264 ];
265 const OUTPUTS_TYPES: &[Type] = &[Type::Object];
267 const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Output];
269 const SHORT_TASK_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
271
272 match name {
273 n if n == TASK_HINT_DISKS => Some(&DISKS_TYPES),
274 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_HINT_FPGA => Some(FPGA_TYPES),
275 n if n == TASK_HINT_GPU => Some(GPU_TYPES),
276 n if use_hidden_types
277 && version >= SupportedVersion::V1(V1::Two)
278 && n == TASK_HINT_INPUTS =>
279 {
280 Some(INPUTS_HIDDEN_TYPES)
281 }
282 n if n == TASK_HINT_INPUTS => Some(INPUTS_TYPES),
283 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_HINT_LOCALIZATION_OPTIONAL => {
284 Some(LOCALIZATION_OPTIONAL_TYPES)
285 }
286 n if n == TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS => Some(LOCALIZATION_OPTIONAL_TYPES),
287 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_HINT_MAX_CPU => {
288 Some(MAX_CPU_TYPES)
289 }
290 n if n == TASK_HINT_MAX_CPU_ALIAS => Some(MAX_CPU_TYPES),
291 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_HINT_MAX_MEMORY => {
292 Some(MAX_MEMORY_TYPES)
293 }
294 n if n == TASK_HINT_MAX_MEMORY_ALIAS => Some(MAX_MEMORY_TYPES),
295 n if use_hidden_types
296 && version >= SupportedVersion::V1(V1::Two)
297 && n == TASK_HINT_OUTPUTS =>
298 {
299 Some(OUTPUTS_HIDDEN_TYPES)
300 }
301 n if n == TASK_HINT_OUTPUTS => Some(OUTPUTS_TYPES),
302 n if version >= SupportedVersion::V1(V1::Two) && n == TASK_HINT_SHORT_TASK => {
303 Some(SHORT_TASK_TYPES)
304 }
305 n if n == TASK_HINT_SHORT_TASK_ALIAS => Some(SHORT_TASK_TYPES),
306 _ => None,
307 }
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum ComparisonOperator {
313 Equality,
315 Inequality,
317 Less,
319 LessEqual,
321 Greater,
323 GreaterEqual,
325}
326
327impl fmt::Display for ComparisonOperator {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(
330 f,
331 "{}",
332 match self {
333 Self::Equality => "==",
334 Self::Inequality => "!=",
335 Self::Less => "<",
336 Self::LessEqual => "<=",
337 Self::Greater => ">",
338 Self::GreaterEqual => ">=",
339 }
340 )
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub enum NumericOperator {
347 Addition,
349 Subtraction,
351 Multiplication,
353 Division,
355 Modulo,
357 Exponentiation,
359}
360
361impl fmt::Display for NumericOperator {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 write!(
364 f,
365 "{}",
366 match self {
367 Self::Addition => "addition",
368 Self::Subtraction => "subtraction",
369 Self::Multiplication => "multiplication",
370 Self::Division => "division",
371 Self::Modulo => "remainder",
372 Self::Exponentiation => "exponentiation",
373 }
374 )
375 }
376}
377
378#[derive(Debug)]
380pub struct AstTypeConverter<R>(R);
381
382impl<R> AstTypeConverter<R>
383where
384 R: TypeNameResolver,
385{
386 pub fn new(resolver: R) -> Self {
388 Self(resolver)
389 }
390
391 pub fn convert_type<N: TreeNode>(&mut self, ty: &v1::Type<N>) -> Result<Type, Diagnostic> {
396 let optional = ty.is_optional();
397
398 let ty: Type = match ty {
399 v1::Type::Map(ty) => {
400 let ty = self.convert_map_type(ty)?;
401 ty.into()
402 }
403 v1::Type::Array(ty) => {
404 let ty = self.convert_array_type(ty)?;
405 ty.into()
406 }
407 v1::Type::Pair(ty) => {
408 let ty = self.convert_pair_type(ty)?;
409 ty.into()
410 }
411 v1::Type::Object(_) => Type::Object,
412 v1::Type::Ref(r) => {
413 let name = r.name();
414 self.0.resolve(name.text(), name.span())?
415 }
416 v1::Type::Primitive(ty) => Type::Primitive(ty.kind().into(), false),
417 };
418
419 if optional { Ok(ty.optional()) } else { Ok(ty) }
420 }
421
422 pub fn convert_array_type<N: TreeNode>(
427 &mut self,
428 ty: &v1::ArrayType<N>,
429 ) -> Result<ArrayType, Diagnostic> {
430 let element_type = self.convert_type(&ty.element_type())?;
431 if ty.is_non_empty() {
432 Ok(ArrayType::non_empty(element_type))
433 } else {
434 Ok(ArrayType::new(element_type))
435 }
436 }
437
438 pub fn convert_pair_type<N: TreeNode>(
443 &mut self,
444 ty: &v1::PairType<N>,
445 ) -> Result<PairType, Diagnostic> {
446 let (left_type, right_type) = ty.types();
447 Ok(PairType::new(
448 self.convert_type(&left_type)?,
449 self.convert_type(&right_type)?,
450 ))
451 }
452
453 pub fn convert_map_type<N: TreeNode>(
458 &mut self,
459 ty: &v1::MapType<N>,
460 ) -> Result<MapType, Diagnostic> {
461 let (key_type, value_type) = ty.types();
462 let optional = key_type.is_optional();
463 Ok(MapType::new(
464 Type::Primitive(key_type.kind().into(), optional),
465 self.convert_type(&value_type)?,
466 ))
467 }
468
469 pub fn convert_struct_type<N: TreeNode>(
474 &mut self,
475 definition: &v1::StructDefinition<N>,
476 ) -> Result<StructType, Diagnostic> {
477 Ok(StructType {
478 name: Arc::new(definition.name().text().to_string()),
479 members: definition
480 .members()
481 .map(|d| Ok((d.name().text().to_string(), self.convert_type(&d.ty())?)))
482 .collect::<Result<_, _>>()?,
483 })
484 }
485}
486
487impl From<v1::PrimitiveTypeKind> for PrimitiveType {
488 fn from(value: v1::PrimitiveTypeKind) -> Self {
489 match value {
490 v1::PrimitiveTypeKind::Boolean => Self::Boolean,
491 v1::PrimitiveTypeKind::Integer => Self::Integer,
492 v1::PrimitiveTypeKind::Float => Self::Float,
493 v1::PrimitiveTypeKind::String => Self::String,
494 v1::PrimitiveTypeKind::File => Self::File,
495 v1::PrimitiveTypeKind::Directory => Self::Directory,
496 }
497 }
498}
499
500pub trait EvaluationContext {
502 fn version(&self) -> SupportedVersion;
504
505 fn resolve_name(&self, name: &str, span: Span) -> Option<Type>;
507
508 fn resolve_type_name(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
510
511 fn task(&self) -> Option<&Task>;
515
516 fn diagnostics_config(&self) -> DiagnosticsConfig;
518
519 fn add_diagnostic(&mut self, diagnostic: Diagnostic);
521}
522
523#[derive(Debug)]
525pub struct ExprTypeEvaluator<'a, C> {
526 context: &'a mut C,
528 placeholders: usize,
536}
537
538impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
539 pub fn new(context: &'a mut C) -> Self {
541 Self {
542 context,
543 placeholders: 0,
544 }
545 }
546
547 pub fn evaluate_expr<N: TreeNode + SyntaxNodeExt>(&mut self, expr: &Expr<N>) -> Option<Type> {
551 match expr {
552 Expr::Literal(expr) => self.evaluate_literal_expr(expr),
553 Expr::NameRef(r) => {
554 let name = r.name();
555 self.context.resolve_name(name.text(), name.span())
556 }
557 Expr::Parenthesized(expr) => self.evaluate_expr(&expr.expr()),
558 Expr::If(expr) => self.evaluate_if_expr(expr),
559 Expr::LogicalNot(expr) => self.evaluate_logical_not_expr(expr),
560 Expr::Negation(expr) => self.evaluate_negation_expr(expr),
561 Expr::LogicalOr(expr) => self.evaluate_logical_or_expr(expr),
562 Expr::LogicalAnd(expr) => self.evaluate_logical_and_expr(expr),
563 Expr::Equality(expr) => {
564 let (lhs, rhs) = expr.operands();
565 self.evaluate_comparison_expr(ComparisonOperator::Equality, &lhs, &rhs, expr.span())
566 }
567 Expr::Inequality(expr) => {
568 let (lhs, rhs) = expr.operands();
569 self.evaluate_comparison_expr(
570 ComparisonOperator::Inequality,
571 &lhs,
572 &rhs,
573 expr.span(),
574 )
575 }
576 Expr::Less(expr) => {
577 let (lhs, rhs) = expr.operands();
578 self.evaluate_comparison_expr(ComparisonOperator::Less, &lhs, &rhs, expr.span())
579 }
580 Expr::LessEqual(expr) => {
581 let (lhs, rhs) = expr.operands();
582 self.evaluate_comparison_expr(
583 ComparisonOperator::LessEqual,
584 &lhs,
585 &rhs,
586 expr.span(),
587 )
588 }
589 Expr::Greater(expr) => {
590 let (lhs, rhs) = expr.operands();
591 self.evaluate_comparison_expr(ComparisonOperator::Greater, &lhs, &rhs, expr.span())
592 }
593 Expr::GreaterEqual(expr) => {
594 let (lhs, rhs) = expr.operands();
595 self.evaluate_comparison_expr(
596 ComparisonOperator::GreaterEqual,
597 &lhs,
598 &rhs,
599 expr.span(),
600 )
601 }
602 Expr::Addition(expr) => {
603 let (lhs, rhs) = expr.operands();
604 self.evaluate_numeric_expr(NumericOperator::Addition, expr.span(), &lhs, &rhs)
605 }
606 Expr::Subtraction(expr) => {
607 let (lhs, rhs) = expr.operands();
608 self.evaluate_numeric_expr(NumericOperator::Subtraction, expr.span(), &lhs, &rhs)
609 }
610 Expr::Multiplication(expr) => {
611 let (lhs, rhs) = expr.operands();
612 self.evaluate_numeric_expr(NumericOperator::Multiplication, expr.span(), &lhs, &rhs)
613 }
614 Expr::Division(expr) => {
615 let (lhs, rhs) = expr.operands();
616 self.evaluate_numeric_expr(NumericOperator::Division, expr.span(), &lhs, &rhs)
617 }
618 Expr::Modulo(expr) => {
619 let (lhs, rhs) = expr.operands();
620 self.evaluate_numeric_expr(NumericOperator::Modulo, expr.span(), &lhs, &rhs)
621 }
622 Expr::Exponentiation(expr) => {
623 let (lhs, rhs) = expr.operands();
624 self.evaluate_numeric_expr(NumericOperator::Exponentiation, expr.span(), &lhs, &rhs)
625 }
626 Expr::Call(expr) => self.evaluate_call_expr(expr),
627 Expr::Index(expr) => self.evaluate_index_expr(expr),
628 Expr::Access(expr) => self.evaluate_access_expr(expr),
629 }
630 }
631
632 fn evaluate_literal_expr<N: TreeNode + SyntaxNodeExt>(
634 &mut self,
635 expr: &LiteralExpr<N>,
636 ) -> Option<Type> {
637 match expr {
638 LiteralExpr::Boolean(_) => Some(PrimitiveType::Boolean.into()),
639 LiteralExpr::Integer(_) => Some(PrimitiveType::Integer.into()),
640 LiteralExpr::Float(_) => Some(PrimitiveType::Float.into()),
641 LiteralExpr::String(s) => {
642 for p in s.parts() {
643 if let StringPart::Placeholder(p) = p {
644 self.check_placeholder(&p);
645 }
646 }
647
648 Some(PrimitiveType::String.into())
649 }
650 LiteralExpr::Array(expr) => Some(self.evaluate_literal_array(expr)),
651 LiteralExpr::Pair(expr) => Some(self.evaluate_literal_pair(expr)),
652 LiteralExpr::Map(expr) => Some(self.evaluate_literal_map(expr)),
653 LiteralExpr::Object(expr) => Some(self.evaluate_literal_object(expr)),
654 LiteralExpr::Struct(expr) => self.evaluate_literal_struct(expr),
655 LiteralExpr::None(_) => Some(Type::None),
656 LiteralExpr::Hints(expr) => self.evaluate_literal_hints(expr),
657 LiteralExpr::Input(expr) => self.evaluate_literal_input(expr),
658 LiteralExpr::Output(expr) => self.evaluate_literal_output(expr),
659 }
660 }
661
662 pub(crate) fn check_placeholder<N: TreeNode + SyntaxNodeExt>(
664 &mut self,
665 placeholder: &Placeholder<N>,
666 ) {
667 self.placeholders += 1;
668
669 let expr = placeholder.expr();
672 if let Some(ty) = self.evaluate_expr(&expr) {
673 if let Some(option) = placeholder.option() {
674 let valid = match option {
675 PlaceholderOption::Sep(_) => {
676 ty == Type::Union
677 || ty == Type::None
678 || matches!(&ty,
679 Type::Compound(CompoundType::Array(array_ty), _)
680 if matches!(array_ty.element_type(), Type::Primitive(_, false) | Type::Union))
681 }
682 PlaceholderOption::Default(_) => {
683 matches!(ty, Type::Primitive(..) | Type::Union | Type::None)
684 }
685 PlaceholderOption::TrueFalse(_) => {
686 matches!(
687 ty,
688 Type::Primitive(PrimitiveType::Boolean, _) | Type::Union | Type::None
689 )
690 }
691 };
692
693 if !valid {
694 self.context.add_diagnostic(invalid_placeholder_option(
695 &ty,
696 expr.span(),
697 &option,
698 ));
699 }
700 } else {
701 match ty {
702 Type::Primitive(..) | Type::Union | Type::None => {}
703 _ => {
704 self.context
705 .add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
706 }
707 }
708 }
709 }
710
711 self.placeholders -= 1;
712 }
713
714 fn evaluate_literal_array<N: TreeNode + SyntaxNodeExt>(
716 &mut self,
717 expr: &LiteralArray<N>,
718 ) -> Type {
719 let mut elements = expr.elements();
722 match elements
723 .next()
724 .and_then(|e| Some((self.evaluate_expr(&e)?, e.span())))
725 {
726 Some((mut expected, mut expected_span)) => {
727 for expr in elements {
729 if let Some(actual) = self.evaluate_expr(&expr) {
730 match expected.common_type(&actual) {
731 Some(ty) => {
732 expected = ty;
733 expected_span = expr.span();
734 }
735 _ => {
736 self.context.add_diagnostic(no_common_type(
737 &expected,
738 expected_span,
739 &actual,
740 expr.span(),
741 ));
742 }
743 }
744 }
745 }
746
747 ArrayType::non_empty(expected).into()
748 }
749 None => ArrayType::new(Type::Union).into(),
751 }
752 }
753
754 fn evaluate_literal_pair<N: TreeNode + SyntaxNodeExt>(
756 &mut self,
757 expr: &LiteralPair<N>,
758 ) -> Type {
759 let (left, right) = expr.exprs();
760 let left = self.evaluate_expr(&left).unwrap_or(Type::Union);
761 let right = self.evaluate_expr(&right).unwrap_or(Type::Union);
762 PairType::new(left, right).into()
763 }
764
765 fn evaluate_literal_map<N: TreeNode + SyntaxNodeExt>(&mut self, expr: &LiteralMap<N>) -> Type {
767 let map_item_type = |item: LiteralMapItem<N>| {
768 let (key, value) = item.key_value();
769 let expected_key = self.evaluate_expr(&key)?;
770 match expected_key {
771 Type::Primitive(..) | Type::None | Type::Union => {
772 }
774 _ => {
775 self.context
776 .add_diagnostic(map_key_not_primitive(key.span(), &expected_key));
777 return None;
778 }
779 }
780
781 Some((
782 expected_key,
783 key.span(),
784 self.evaluate_expr(&value)?,
785 value.span(),
786 ))
787 };
788
789 let mut items = expr.items();
790 match items.next().and_then(map_item_type) {
791 Some((
792 mut expected_key,
793 mut expected_key_span,
794 mut expected_value,
795 mut expected_value_span,
796 )) => {
797 for item in items {
799 let (key, value) = item.key_value();
800 if let Some(actual_key) = self.evaluate_expr(&key) {
801 if let Some(actual_value) = self.evaluate_expr(&value) {
802 match expected_key.common_type(&actual_key) {
803 Some(ty) => {
804 expected_key = ty;
805 expected_key_span = key.span();
806 }
807 _ => {
808 self.context.add_diagnostic(no_common_type(
809 &expected_key,
810 expected_key_span,
811 &actual_key,
812 key.span(),
813 ));
814 }
815 }
816
817 match expected_value.common_type(&actual_value) {
818 Some(ty) => {
819 expected_value = ty;
820 expected_value_span = value.span();
821 }
822 _ => {
823 self.context.add_diagnostic(no_common_type(
824 &expected_value,
825 expected_value_span,
826 &actual_value,
827 value.span(),
828 ));
829 }
830 }
831 }
832 }
833 }
834
835 MapType::new(expected_key, expected_value).into()
836 }
837 None => MapType::new(Type::Union, Type::Union).into(),
839 }
840 }
841
842 fn evaluate_literal_object<N: TreeNode + SyntaxNodeExt>(
844 &mut self,
845 expr: &LiteralObject<N>,
846 ) -> Type {
847 for item in expr.items() {
849 let (_, v) = item.name_value();
850 self.evaluate_expr(&v);
851 }
852
853 Type::Object
854 }
855
856 fn evaluate_literal_struct<N: TreeNode + SyntaxNodeExt>(
858 &mut self,
859 expr: &LiteralStruct<N>,
860 ) -> Option<Type> {
861 let name = expr.name();
862 match self.context.resolve_type_name(name.text(), name.span()) {
863 Ok(ty) => {
864 let ty = match ty {
865 Type::Compound(CompoundType::Struct(ty), false) => ty,
866 _ => panic!("type should be a required struct"),
867 };
868
869 let mut present = vec![false; ty.members().len()];
871
872 for item in expr.items() {
874 let (n, v) = item.name_value();
875 match ty.members.get_full(n.text()) {
876 Some((index, _, expected)) => {
877 present[index] = true;
878 if let Some(actual) = self.evaluate_expr(&v) {
879 if !actual.is_coercible_to(expected) {
880 self.context.add_diagnostic(type_mismatch(
881 expected,
882 n.span(),
883 &actual,
884 v.span(),
885 ));
886 }
887 }
888 }
889 _ => {
890 self.context
892 .add_diagnostic(not_a_struct_member(name.text(), &n));
893 }
894 }
895 }
896
897 let mut unspecified = present
899 .iter()
900 .enumerate()
901 .filter_map(|(i, present)| {
902 if *present {
903 return None;
904 }
905
906 let (name, ty) = &ty.members.get_index(i).unwrap();
907 if ty.is_optional() {
908 return None;
909 }
910
911 Some(name.as_str())
912 })
913 .peekable();
914
915 if unspecified.peek().is_some() {
916 let mut members = String::new();
917 let mut count = 0;
918 while let Some(member) = unspecified.next() {
919 match (unspecified.peek().is_none(), count) {
920 (true, c) if c > 1 => members.push_str(", and "),
921 (true, 1) => members.push_str(" and "),
922 (false, c) if c > 0 => members.push_str(", "),
923 _ => {}
924 }
925
926 write!(&mut members, "`{member}`").ok();
927 count += 1;
928 }
929
930 self.context
931 .add_diagnostic(missing_struct_members(&name, count, &members));
932 }
933
934 Some(Type::Compound(CompoundType::Struct(ty), false))
935 }
936 Err(diagnostic) => {
937 self.context.add_diagnostic(diagnostic);
938 None
939 }
940 }
941 }
942
943 pub(crate) fn evaluate_runtime_item<N: TreeNode + SyntaxNodeExt>(
945 &mut self,
946 name: &Ident<N::Token>,
947 expr: &Expr<N>,
948 ) {
949 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
950 if !self.evaluate_requirement(name, expr, &expr_ty) {
951 if let Some(expected) = task_hint_types(self.context.version(), name.text(), false) {
954 if !expected
955 .iter()
956 .any(|target| expr_ty.is_coercible_to(target))
957 {
958 self.context.add_diagnostic(multiple_type_mismatch(
959 expected,
960 name.span(),
961 &expr_ty,
962 expr.span(),
963 ));
964 }
965 }
966 }
967 }
968
969 pub(crate) fn evaluate_requirements_item<N: TreeNode + SyntaxNodeExt>(
971 &mut self,
972 name: &Ident<N::Token>,
973 expr: &Expr<N>,
974 ) {
975 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
976 self.evaluate_requirement(name, expr, &expr_ty);
977 }
978
979 fn evaluate_requirement<N: TreeNode>(
985 &mut self,
986 name: &Ident<N::Token>,
987 expr: &Expr<N>,
988 expr_ty: &Type,
989 ) -> bool {
990 if let Some(expected) = task_requirement_types(self.context.version(), name.text()) {
991 if !expected
992 .iter()
993 .any(|target| expr_ty.is_coercible_to(target))
994 {
995 self.context.add_diagnostic(multiple_type_mismatch(
996 expected,
997 name.span(),
998 expr_ty,
999 expr.span(),
1000 ));
1001 }
1002
1003 return true;
1004 }
1005
1006 false
1007 }
1008
1009 fn evaluate_literal_hints<N: TreeNode + SyntaxNodeExt>(
1011 &mut self,
1012 expr: &LiteralHints<N>,
1013 ) -> Option<Type> {
1014 self.context.task()?;
1015
1016 for item in expr.items() {
1017 self.evaluate_hints_item(&item.name(), &item.expr())
1018 }
1019
1020 Some(Type::Hints)
1021 }
1022
1023 pub(crate) fn evaluate_hints_item<N: TreeNode + SyntaxNodeExt>(
1026 &mut self,
1027 name: &Ident<N::Token>,
1028 expr: &Expr<N>,
1029 ) {
1030 let expr_ty = self.evaluate_expr(expr).unwrap_or(Type::Union);
1031 if let Some(expected) = task_hint_types(self.context.version(), name.text(), true) {
1032 if !expected
1033 .iter()
1034 .any(|target| expr_ty.is_coercible_to(target))
1035 {
1036 self.context.add_diagnostic(multiple_type_mismatch(
1037 expected,
1038 name.span(),
1039 &expr_ty,
1040 expr.span(),
1041 ));
1042 }
1043 }
1044 }
1045
1046 fn evaluate_literal_input<N: TreeNode + SyntaxNodeExt>(
1048 &mut self,
1049 expr: &LiteralInput<N>,
1050 ) -> Option<Type> {
1051 self.context.task()?;
1053
1054 for item in expr.items() {
1056 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Input);
1057 }
1058
1059 Some(Type::Input)
1060 }
1061
1062 fn evaluate_literal_output<N: TreeNode + SyntaxNodeExt>(
1064 &mut self,
1065 expr: &LiteralOutput<N>,
1066 ) -> Option<Type> {
1067 self.context.task()?;
1069
1070 for item in expr.items() {
1072 self.evaluate_literal_io_item(item.names(), item.expr(), Io::Output);
1073 }
1074
1075 Some(Type::Output)
1076 }
1077
1078 fn evaluate_literal_io_item<N: TreeNode + SyntaxNodeExt>(
1080 &mut self,
1081 names: impl Iterator<Item = Ident<N::Token>>,
1082 expr: Expr<N>,
1083 io: Io,
1084 ) {
1085 let mut names = names.enumerate().peekable();
1086 let expr_ty = self.evaluate_expr(&expr).unwrap_or(Type::Union);
1087
1088 let mut span = None;
1091 let mut s: Option<&StructType> = None;
1092 while let Some((i, name)) = names.next() {
1093 let ty = if i == 0 {
1095 span = Some(name.span());
1096
1097 match if io == Io::Input {
1098 self.context
1099 .task()
1100 .expect("should have task")
1101 .inputs()
1102 .get(name.text())
1103 .map(|i| i.ty())
1104 } else {
1105 self.context
1106 .task()
1107 .expect("should have task")
1108 .outputs()
1109 .get(name.text())
1110 .map(|o| o.ty())
1111 } {
1112 Some(ty) => ty,
1113 None => {
1114 self.context.add_diagnostic(unknown_task_io(
1115 self.context.task().expect("should have task").name(),
1116 &name,
1117 io,
1118 ));
1119 break;
1120 }
1121 }
1122 } else {
1123 let start = span.unwrap().start();
1125 span = Some(Span::new(start, name.span().end() - start));
1126 let s = s.unwrap();
1127 match s.members.get(name.text()) {
1128 Some(ty) => ty,
1129 None => {
1130 self.context
1131 .add_diagnostic(not_a_struct_member(&s.name, &name));
1132 break;
1133 }
1134 }
1135 };
1136
1137 match ty {
1138 Type::Compound(CompoundType::Struct(ty), _) => s = Some(ty),
1139 _ if names.peek().is_some() => {
1140 self.context.add_diagnostic(not_a_struct(&name, i == 0));
1141 break;
1142 }
1143 _ => {
1144 }
1146 }
1147 }
1148
1149 if let Some((_, last)) = names.last() {
1151 let start = span.unwrap().start();
1152 span = Some(Span::new(start, last.span().end() - start));
1153 }
1154
1155 if !expr_ty.is_coercible_to(&Type::Hints) {
1157 self.context.add_diagnostic(type_mismatch(
1158 &Type::Hints,
1159 span.expect("should have span"),
1160 &expr_ty,
1161 expr.span(),
1162 ));
1163 }
1164 }
1165
1166 fn evaluate_if_expr<N: TreeNode + SyntaxNodeExt>(&mut self, expr: &IfExpr<N>) -> Option<Type> {
1168 let (cond_expr, true_expr, false_expr) = expr.exprs();
1169
1170 let cond_ty = self.evaluate_expr(&cond_expr).unwrap_or(Type::Union);
1172 if !cond_ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1173 self.context
1174 .add_diagnostic(if_conditional_mismatch(&cond_ty, cond_expr.span()));
1175 }
1176
1177 let true_ty = self.evaluate_expr(&true_expr).unwrap_or(Type::Union);
1179 let false_ty = self.evaluate_expr(&false_expr).unwrap_or(Type::Union);
1180
1181 match (true_ty, false_ty) {
1182 (Type::Union, Type::Union) => None,
1183 (Type::Union, false_ty) => Some(false_ty),
1184 (true_ty, Type::Union) => Some(true_ty),
1185 (true_ty, false_ty) => match true_ty.common_type(&false_ty) {
1186 Some(ty) => Some(ty),
1187 _ => {
1188 self.context.add_diagnostic(type_mismatch(
1189 &true_ty,
1190 true_expr.span(),
1191 &false_ty,
1192 false_expr.span(),
1193 ));
1194
1195 None
1196 }
1197 },
1198 }
1199 }
1200
1201 fn evaluate_logical_not_expr<N: TreeNode + SyntaxNodeExt>(
1203 &mut self,
1204 expr: &LogicalNotExpr<N>,
1205 ) -> Option<Type> {
1206 let operand = expr.operand();
1208 let ty = self.evaluate_expr(&operand).unwrap_or(Type::Union);
1209 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1210 self.context
1211 .add_diagnostic(logical_not_mismatch(&ty, operand.span()));
1212 }
1213
1214 Some(PrimitiveType::Boolean.into())
1215 }
1216
1217 fn evaluate_negation_expr<N: TreeNode + SyntaxNodeExt>(
1219 &mut self,
1220 expr: &NegationExpr<N>,
1221 ) -> Option<Type> {
1222 let operand = expr.operand();
1224 let ty = self.evaluate_expr(&operand)?;
1225
1226 if ty.eq(&PrimitiveType::Integer.into()) {
1229 return Some(PrimitiveType::Integer.into());
1230 }
1231
1232 if !ty.is_coercible_to(&PrimitiveType::Float.into()) {
1233 self.context
1234 .add_diagnostic(negation_mismatch(&ty, operand.span()));
1235 return None;
1237 }
1238
1239 Some(PrimitiveType::Float.into())
1240 }
1241
1242 fn evaluate_logical_or_expr<N: TreeNode + SyntaxNodeExt>(
1244 &mut self,
1245 expr: &LogicalOrExpr<N>,
1246 ) -> Option<Type> {
1247 let (lhs, rhs) = expr.operands();
1249
1250 let ty = self.evaluate_expr(&lhs).unwrap_or(Type::Union);
1251 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1252 self.context
1253 .add_diagnostic(logical_or_mismatch(&ty, lhs.span()));
1254 }
1255
1256 let ty = self.evaluate_expr(&rhs).unwrap_or(Type::Union);
1257 if !ty.is_coercible_to(&PrimitiveType::Boolean.into()) {
1258 self.context
1259 .add_diagnostic(logical_or_mismatch(&ty, rhs.span()));
1260 }
1261
1262 Some(PrimitiveType::Boolean.into())
1263 }
1264
1265 fn evaluate_logical_and_expr<N: TreeNode + SyntaxNodeExt>(
1267 &mut self,
1268 expr: &LogicalAndExpr<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_and_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_and_mismatch(&ty, rhs.span()));
1283 }
1284
1285 Some(PrimitiveType::Boolean.into())
1286 }
1287
1288 fn evaluate_comparison_expr<N: TreeNode + SyntaxNodeExt>(
1290 &mut self,
1291 op: ComparisonOperator,
1292 lhs: &Expr<N>,
1293 rhs: &Expr<N>,
1294 span: Span,
1295 ) -> Option<Type> {
1296 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1297 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1298
1299 if lhs_ty.is_union() || lhs_ty.is_none() || rhs_ty.is_union() || rhs_ty.is_none() {
1301 return Some(PrimitiveType::Boolean.into());
1302 }
1303
1304 for expected in [
1306 Type::from(PrimitiveType::Boolean),
1307 PrimitiveType::Integer.into(),
1308 PrimitiveType::Float.into(),
1309 PrimitiveType::String.into(),
1310 PrimitiveType::File.into(),
1311 PrimitiveType::Directory.into(),
1312 ] {
1313 if op != ComparisonOperator::Equality
1315 && op != ComparisonOperator::Inequality
1316 && (matches!(
1317 lhs_ty.as_primitive(),
1318 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1319 ) || matches!(
1320 rhs_ty.as_primitive(),
1321 Some(PrimitiveType::File) | Some(PrimitiveType::Directory)
1322 ))
1323 {
1324 continue;
1325 }
1326
1327 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1328 return Some(PrimitiveType::Boolean.into());
1329 }
1330
1331 let expected = expected.optional();
1332 if lhs_ty.is_coercible_to(&expected) && rhs_ty.is_coercible_to(&expected) {
1333 return Some(PrimitiveType::Boolean.into());
1334 }
1335 }
1336
1337 if op == ComparisonOperator::Equality || op == ComparisonOperator::Inequality {
1339 if (lhs_ty.is_coercible_to(&Type::Object) && rhs_ty.is_coercible_to(&Type::Object))
1341 || (lhs_ty.is_coercible_to(&Type::OptionalObject)
1342 && rhs_ty.is_coercible_to(&Type::OptionalObject))
1343 {
1344 return Some(PrimitiveType::Boolean.into());
1345 }
1346
1347 let equal = match (&lhs_ty, &rhs_ty) {
1349 (
1350 Type::Compound(CompoundType::Array(a), _),
1351 Type::Compound(CompoundType::Array(b), _),
1352 ) => a == b,
1353 (
1354 Type::Compound(CompoundType::Pair(a), _),
1355 Type::Compound(CompoundType::Pair(b), _),
1356 ) => a == b,
1357 (
1358 Type::Compound(CompoundType::Map(a), _),
1359 Type::Compound(CompoundType::Map(b), _),
1360 ) => a == b,
1361 (
1362 Type::Compound(CompoundType::Struct(a), _),
1363 Type::Compound(CompoundType::Struct(b), _),
1364 ) => a == b,
1365 _ => false,
1366 };
1367
1368 if equal {
1369 return Some(PrimitiveType::Boolean.into());
1370 }
1371 }
1372
1373 self.context.add_diagnostic(comparison_mismatch(
1375 op,
1376 span,
1377 &lhs_ty,
1378 lhs.span(),
1379 &rhs_ty,
1380 rhs.span(),
1381 ));
1382 Some(PrimitiveType::Boolean.into())
1383 }
1384
1385 fn evaluate_numeric_expr<N: TreeNode + SyntaxNodeExt>(
1387 &mut self,
1388 op: NumericOperator,
1389 span: Span,
1390 lhs: &Expr<N>,
1391 rhs: &Expr<N>,
1392 ) -> Option<Type> {
1393 let lhs_ty = self.evaluate_expr(lhs).unwrap_or(Type::Union);
1394 let rhs_ty = self.evaluate_expr(rhs).unwrap_or(Type::Union);
1395
1396 if lhs_ty.eq(&PrimitiveType::Integer.into()) && rhs_ty.eq(&PrimitiveType::Integer.into()) {
1398 return Some(PrimitiveType::Integer.into());
1399 }
1400
1401 if !lhs_ty.is_union()
1403 && lhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1404 && !rhs_ty.is_union()
1405 && rhs_ty.is_coercible_to(&PrimitiveType::Float.into())
1406 {
1407 return Some(PrimitiveType::Float.into());
1408 }
1409
1410 if op == NumericOperator::Addition {
1414 let allow_optional = self.placeholders > 0;
1415 let other = if (!lhs_ty.is_optional() || allow_optional)
1416 && lhs_ty
1417 .as_primitive()
1418 .map(|p| p == PrimitiveType::String)
1419 .unwrap_or(false)
1420 {
1421 Some((lhs_ty.is_optional(), &rhs_ty, rhs.span()))
1422 } else if (!rhs_ty.is_optional() || allow_optional)
1423 && rhs_ty
1424 .as_primitive()
1425 .map(|p| p == PrimitiveType::String)
1426 .unwrap_or(false)
1427 {
1428 Some((rhs_ty.is_optional(), &lhs_ty, lhs.span()))
1429 } else {
1430 None
1431 };
1432
1433 if let Some((optional, other, span)) = other {
1434 if (!other.is_optional() || allow_optional)
1435 && other
1436 .as_primitive()
1437 .map(|p| p != PrimitiveType::Boolean)
1438 .unwrap_or(other.is_union() || (allow_optional && other.is_none()))
1439 {
1440 let ty: Type = PrimitiveType::String.into();
1441 if optional || other.is_optional() {
1442 return Some(ty.optional());
1443 }
1444
1445 return Some(ty);
1446 }
1447
1448 self.context
1449 .add_diagnostic(string_concat_mismatch(other, span));
1450 return None;
1451 }
1452 }
1453
1454 if !lhs_ty.is_union() && !rhs_ty.is_union() {
1455 self.context.add_diagnostic(numeric_mismatch(
1456 op,
1457 span,
1458 &lhs_ty,
1459 lhs.span(),
1460 &rhs_ty,
1461 rhs.span(),
1462 ));
1463 }
1464
1465 None
1466 }
1467
1468 fn evaluate_call_expr<N: TreeNode + SyntaxNodeExt>(
1470 &mut self,
1471 expr: &CallExpr<N>,
1472 ) -> Option<Type> {
1473 let target = expr.target();
1474 match STDLIB.function(target.text()) {
1475 Some(f) => {
1476 let mut count = 0;
1478 let mut arguments = [const { Type::Union }; MAX_PARAMETERS];
1479
1480 for arg in expr.arguments() {
1481 if count < MAX_PARAMETERS {
1482 arguments[count] = self.evaluate_expr(&arg).unwrap_or(Type::Union);
1483 }
1484
1485 count += 1;
1486 }
1487
1488 match target.text() {
1489 "find" | "matches" | "sub" => {
1490 if let Some(Expr::Literal(LiteralExpr::String(pattern_literal))) =
1492 expr.arguments().nth(1)
1493 {
1494 if let Some(value) = pattern_literal.text() {
1495 let pattern = value.text().to_string();
1496 if let Err(e) = regex::Regex::new(&pattern) {
1497 self.context.add_diagnostic(invalid_regex_pattern(
1498 target.text(),
1499 value.text(),
1500 &e,
1501 pattern_literal.span(),
1502 ));
1503 }
1504 }
1505 }
1506 }
1507 _ => {}
1508 }
1509
1510 let arguments = &arguments[..count.min(MAX_PARAMETERS)];
1511 if count <= MAX_PARAMETERS {
1512 match f.bind(self.context.version(), arguments) {
1513 Ok(binding) => {
1514 if let Some(severity) =
1515 self.context.diagnostics_config().unnecessary_function_call
1516 {
1517 if !expr.inner().is_rule_excepted(UNNECESSARY_FUNCTION_CALL) {
1518 self.check_unnecessary_call(
1519 &target,
1520 arguments,
1521 expr.arguments().map(|e| e.span()),
1522 severity,
1523 );
1524 }
1525 }
1526 return Some(binding.return_type().clone());
1527 }
1528 Err(FunctionBindError::RequiresVersion(minimum)) => {
1529 self.context.add_diagnostic(unsupported_function(
1530 minimum,
1531 target.text(),
1532 target.span(),
1533 ));
1534 }
1535 Err(FunctionBindError::TooFewArguments(minimum)) => {
1536 self.context.add_diagnostic(too_few_arguments(
1537 target.text(),
1538 target.span(),
1539 minimum,
1540 count,
1541 ));
1542 }
1543 Err(FunctionBindError::TooManyArguments(maximum)) => {
1544 self.context.add_diagnostic(too_many_arguments(
1545 target.text(),
1546 target.span(),
1547 maximum,
1548 count,
1549 expr.arguments().skip(maximum).map(|e| e.span()),
1550 ));
1551 }
1552 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1553 self.context.add_diagnostic(argument_type_mismatch(
1554 target.text(),
1555 &expected,
1556 &arguments[index],
1557 expr.arguments()
1558 .nth(index)
1559 .map(|e| e.span())
1560 .expect("should have span"),
1561 ));
1562 }
1563 Err(FunctionBindError::Ambiguous { first, second }) => {
1564 self.context.add_diagnostic(ambiguous_argument(
1565 target.text(),
1566 target.span(),
1567 &first,
1568 &second,
1569 ));
1570 }
1571 }
1572 } else {
1573 match f.param_min_max(self.context.version()) {
1575 Some((_, max)) => {
1576 assert!(max <= MAX_PARAMETERS);
1577 self.context.add_diagnostic(too_many_arguments(
1578 target.text(),
1579 target.span(),
1580 max,
1581 count,
1582 expr.arguments().skip(max).map(|e| e.span()),
1583 ));
1584 }
1585 None => {
1586 self.context.add_diagnostic(unsupported_function(
1587 f.minimum_version(),
1588 target.text(),
1589 target.span(),
1590 ));
1591 }
1592 }
1593 }
1594
1595 Some(f.realize_unconstrained_return_type(arguments))
1596 }
1597 None => {
1598 self.context
1599 .add_diagnostic(unknown_function(target.text(), target.span()));
1600 None
1601 }
1602 }
1603 }
1604
1605 fn evaluate_index_expr<N: TreeNode + SyntaxNodeExt>(
1607 &mut self,
1608 expr: &IndexExpr<N>,
1609 ) -> Option<Type> {
1610 let (target, index) = expr.operands();
1611
1612 let target_ty = self.evaluate_expr(&target)?;
1614 let (expected_index_ty, result_ty) = match &target_ty {
1615 Type::Compound(CompoundType::Array(ty), _) => (
1616 Some(PrimitiveType::Integer.into()),
1617 Some(ty.element_type().clone()),
1618 ),
1619 Type::Compound(CompoundType::Map(ty), _) => {
1620 (Some(ty.key_type().clone()), Some(ty.value_type().clone()))
1621 }
1622 _ => (None, None),
1623 };
1624
1625 if let Some(expected_index_ty) = expected_index_ty {
1627 let index_ty = self.evaluate_expr(&index).unwrap_or(Type::Union);
1628 if !index_ty.is_coercible_to(&expected_index_ty) {
1629 self.context.add_diagnostic(index_type_mismatch(
1630 &expected_index_ty,
1631 &index_ty,
1632 index.span(),
1633 ));
1634 }
1635 }
1636
1637 match result_ty {
1638 Some(ty) => Some(ty),
1639 None => {
1640 self.context
1641 .add_diagnostic(cannot_index(&target_ty, target.span()));
1642 None
1643 }
1644 }
1645 }
1646
1647 fn evaluate_access_expr<N: TreeNode + SyntaxNodeExt>(
1649 &mut self,
1650 expr: &AccessExpr<N>,
1651 ) -> Option<Type> {
1652 let (target, name) = expr.operands();
1653 let ty = self.evaluate_expr(&target)?;
1654
1655 if matches!(ty, Type::Task) {
1656 return match task_member_type(name.text()) {
1657 Some(ty) => Some(ty),
1658 None => {
1659 self.context.add_diagnostic(not_a_task_member(&name));
1660 return None;
1661 }
1662 };
1663 }
1664
1665 match &ty {
1667 Type::Compound(CompoundType::Struct(ty), _) => {
1668 if let Some(ty) = ty.members.get(name.text()) {
1669 return Some(ty.clone());
1670 }
1671
1672 self.context
1673 .add_diagnostic(not_a_struct_member(ty.name(), &name));
1674 return None;
1675 }
1676 Type::Compound(CompoundType::Pair(ty), _) => {
1677 return match name.text() {
1679 "left" => Some(ty.left_type.clone()),
1680 "right" => Some(ty.right_type.clone()),
1681 _ => {
1682 self.context.add_diagnostic(not_a_pair_accessor(&name));
1683 None
1684 }
1685 };
1686 }
1687 Type::Call(ty) => {
1688 if let Some(output) = ty.outputs().get(name.text()) {
1689 return Some(output.ty().clone());
1690 }
1691
1692 self.context
1693 .add_diagnostic(unknown_call_io(ty, &name, Io::Output));
1694 return None;
1695 }
1696 _ => {}
1697 }
1698
1699 if ty.is_coercible_to(&Type::OptionalObject) {
1702 return Some(Type::Union);
1703 }
1704
1705 self.context
1706 .add_diagnostic(cannot_access(&ty, target.span()));
1707 None
1708 }
1709
1710 fn check_unnecessary_call<T: TreeToken>(
1712 &mut self,
1713 target: &Ident<T>,
1714 arguments: &[Type],
1715 mut spans: impl Iterator<Item = Span>,
1716 severity: Severity,
1717 ) {
1718 let (label, span, fix) = match target.text() {
1719 "select_first" => {
1720 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1721 if ty.is_optional() || ty.is_union() {
1722 return;
1723 }
1724 (
1725 format!("array element type `{ty}` is not optional"),
1726 spans.next().expect("should have span"),
1727 "replace the function call with the array's first element",
1728 )
1729 } else {
1730 return;
1731 }
1732 }
1733 "select_all" => {
1734 if let Some(ty) = arguments[0].as_array().map(|a| a.element_type()) {
1735 if ty.is_optional() || ty.is_union() {
1736 return;
1737 }
1738 (
1739 format!("array element type `{ty}` is not optional"),
1740 spans.next().expect("should have span"),
1741 "replace the function call with the array itself",
1742 )
1743 } else {
1744 return;
1745 }
1746 }
1747 "defined" => {
1748 if arguments[0].is_optional() || arguments[0].is_union() {
1749 return;
1750 }
1751
1752 (
1753 format!("type `{ty}` is not optional", ty = arguments[0]),
1754 spans.next().expect("should have span"),
1755 "replace the function call with `true`",
1756 )
1757 }
1758 _ => return,
1759 };
1760
1761 self.context.add_diagnostic(
1762 unnecessary_function_call(target.text(), target.span(), &label, span)
1763 .with_severity(severity)
1764 .with_fix(fix),
1765 )
1766 }
1767}