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