wdl_analysis/types/
v1.rs

1//! Type conversion helpers for a V1 AST.
2
3use 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;
130/// Gets the type of a `task` variable member type.
131///
132/// `task` variables are supported in command and output sections in WDL 1.2.
133///
134/// Returns `None` if the given member name is unknown.
135pub 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
157/// Gets the types of a task requirement.
158///
159/// Returns a slice of types or `None` if the given name is not a requirement.
160pub fn task_requirement_types(version: SupportedVersion, name: &str) -> Option<&'static [Type]> {
161    /// The types for the `container` requirement.
162    static CONTAINER_TYPES: LazyLock<Box<[Type]>> = LazyLock::new(|| {
163        Box::new([
164            PrimitiveType::String.into(),
165            STDLIB.array_string_type().clone(),
166        ])
167    });
168    /// The types for the `cpu` requirement.
169    const CPU_TYPES: &[Type] = &[
170        Type::Primitive(PrimitiveType::Integer, false),
171        Type::Primitive(PrimitiveType::Float, false),
172    ];
173    /// The types for the `memory` requirement.
174    const MEMORY_TYPES: &[Type] = &[
175        Type::Primitive(PrimitiveType::Integer, false),
176        Type::Primitive(PrimitiveType::String, false),
177    ];
178    /// The types for the `gpu` requirement.
179    const GPU_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
180    /// The types for the `fpga` requirement.
181    const FPGA_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
182    /// The types for the `disks` requirement.
183    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    /// The types for the `max_retries` requirement.
191    const MAX_RETRIES_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Integer, false)];
192    /// The types for the `return_codes` requirement.
193    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
224/// Gets the types of a task hint.
225///
226/// Returns a slice of types or `None` if the given name is not a reserved hint.
227pub fn task_hint_types(
228    version: SupportedVersion,
229    name: &str,
230    use_hidden_types: bool,
231) -> Option<&'static [Type]> {
232    /// The types for the `disks` hint.
233    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    /// The types for the `fpga` hint.
240    const FPGA_TYPES: &[Type] = &[
241        Type::Primitive(PrimitiveType::Integer, false),
242        Type::Primitive(PrimitiveType::String, false),
243    ];
244    /// The types for the `gpu` hint.
245    const GPU_TYPES: &[Type] = &[
246        Type::Primitive(PrimitiveType::Integer, false),
247        Type::Primitive(PrimitiveType::String, false),
248    ];
249    /// The types for the `inputs` hint.
250    const INPUTS_TYPES: &[Type] = &[Type::Object];
251    /// The types for the `inputs` hint (with hidden types).
252    const INPUTS_HIDDEN_TYPES: &[Type] = &[Type::Input];
253    /// The types for the `localization_optional` hint.
254    const LOCALIZATION_OPTIONAL_TYPES: &[Type] = &[Type::Primitive(PrimitiveType::Boolean, false)];
255    /// The types for the `max_cpu` hint.
256    const MAX_CPU_TYPES: &[Type] = &[
257        Type::Primitive(PrimitiveType::Integer, false),
258        Type::Primitive(PrimitiveType::Float, false),
259    ];
260    /// The types for the `max_memory` hint.
261    const MAX_MEMORY_TYPES: &[Type] = &[
262        Type::Primitive(PrimitiveType::Integer, false),
263        Type::Primitive(PrimitiveType::String, false),
264    ];
265    /// The types for the `outputs` hint.
266    const OUTPUTS_TYPES: &[Type] = &[Type::Object];
267    /// The types for the `outputs` hint (with hidden types).
268    const OUTPUTS_HIDDEN_TYPES: &[Type] = &[Type::Output];
269    /// The types for the `short_task` hint.
270    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/// Represents a comparison operator.
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum ComparisonOperator {
313    /// The `==` operator.
314    Equality,
315    /// The `!=` operator.
316    Inequality,
317    /// The `>` operator.
318    Less,
319    /// The `<=` operator.
320    LessEqual,
321    /// The `>` operator.
322    Greater,
323    /// The `>=` operator.
324    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/// Represents a numeric operator.
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub enum NumericOperator {
347    /// The `+` operator.
348    Addition,
349    /// The `-` operator.
350    Subtraction,
351    /// The `*` operator.
352    Multiplication,
353    /// The `/` operator.
354    Division,
355    /// The `%` operator.
356    Modulo,
357    /// The `**` operator.
358    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/// Used to convert AST types into diagnostic types.
379#[derive(Debug)]
380pub struct AstTypeConverter<R>(R);
381
382impl<R> AstTypeConverter<R>
383where
384    R: TypeNameResolver,
385{
386    /// Constructs a new AST type converter.
387    pub fn new(resolver: R) -> Self {
388        Self(resolver)
389    }
390
391    /// Converts a V1 AST type into an analysis type.
392    ///
393    /// If a type could not created, an error with the relevant diagnostic is
394    /// returned.
395    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    /// Converts an AST array type to a diagnostic array type.
423    ///
424    /// If a type could not created, an error with the relevant diagnostic is
425    /// returned.
426    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    /// Converts an AST pair type into a diagnostic pair type.
439    ///
440    /// If a type could not created, an error with the relevant diagnostic is
441    /// returned.
442    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    /// Creates an AST map type into a diagnostic map type.
454    ///
455    /// If a type could not created, an error with the relevant diagnostic is
456    /// returned.
457    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    /// Converts an AST struct definition into a struct type.
470    ///
471    /// If the type could not created, an error with the relevant diagnostic is
472    /// returned.
473    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
500/// Represents context to an expression type evaluator.
501pub trait EvaluationContext {
502    /// Gets the supported version of the document being evaluated.
503    fn version(&self) -> SupportedVersion;
504
505    /// Gets the type of the given name in scope.
506    fn resolve_name(&self, name: &str, span: Span) -> Option<Type>;
507
508    /// Resolves a type name to a type.
509    fn resolve_type_name(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
510
511    /// Gets the task associated with the evaluation context.
512    ///
513    /// This is only `Some` when evaluating a task `hints` section.
514    fn task(&self) -> Option<&Task>;
515
516    /// Gets the diagnostics configuration for the evaluation.
517    fn diagnostics_config(&self) -> DiagnosticsConfig;
518
519    /// Adds a diagnostic.
520    fn add_diagnostic(&mut self, diagnostic: Diagnostic);
521}
522
523/// Represents an evaluator of expression types.
524#[derive(Debug)]
525pub struct ExprTypeEvaluator<'a, C> {
526    /// The context for the evaluator.
527    context: &'a mut C,
528    /// The nested count of placeholder evaluation.
529    ///
530    /// This is incremented immediately before a placeholder expression is
531    /// evaluated and decremented immediately after.
532    ///
533    /// If the count is non-zero, special evaluation behavior is enabled for
534    /// string interpolation.
535    placeholders: usize,
536}
537
538impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
539    /// Constructs a new expression type evaluator.
540    pub fn new(context: &'a mut C) -> Self {
541        Self {
542            context,
543            placeholders: 0,
544        }
545    }
546
547    /// Evaluates the type of the given expression in the given scope.
548    ///
549    /// Returns `None` if the type of the expression is indeterminate.
550    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    /// Evaluates the type of a literal expression.
633    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    /// Checks a placeholder expression.
663    pub(crate) fn check_placeholder<N: TreeNode + SyntaxNodeExt>(
664        &mut self,
665        placeholder: &Placeholder<N>,
666    ) {
667        self.placeholders += 1;
668
669        // Evaluate the placeholder expression and check that the resulting type is
670        // coercible to string for interpolation
671        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    /// Evaluates the type of a literal array expression.
715    fn evaluate_literal_array<N: TreeNode + SyntaxNodeExt>(
716        &mut self,
717        expr: &LiteralArray<N>,
718    ) -> Type {
719        // Look at the first array element to determine the element type
720        // The remaining elements must have a common type
721        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                // Ensure the remaining element types share a common type
728                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            // Treat empty array as `Array[Union]`
750            None => ArrayType::new(Type::Union).into(),
751        }
752    }
753
754    /// Evaluates the type of a literal pair expression.
755    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    /// Evaluates the type of a literal map expression.
766    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                    // OK
773                }
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                // Ensure the remaining items types share common types
798                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            // Treat as `Map[Union, Union]`
838            None => MapType::new(Type::Union, Type::Union).into(),
839        }
840    }
841
842    /// Evaluates the type of a literal object expression.
843    fn evaluate_literal_object<N: TreeNode + SyntaxNodeExt>(
844        &mut self,
845        expr: &LiteralObject<N>,
846    ) -> Type {
847        // Validate the member expressions
848        for item in expr.items() {
849            let (_, v) = item.name_value();
850            self.evaluate_expr(&v);
851        }
852
853        Type::Object
854    }
855
856    /// Evaluates the type of a literal struct expression.
857    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                // Keep track of which members are present in the expression
870                let mut present = vec![false; ty.members().len()];
871
872                // Validate the member types
873                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                            // Not a struct member
891                            self.context
892                                .add_diagnostic(not_a_struct_member(name.text(), &n));
893                        }
894                    }
895                }
896
897                // Find the first unspecified member that is required, if any
898                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    /// Evaluates a `runtime` section item.
944    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            // Always use object types for `runtime` section `inputs` and `outputs` keys as
952            // only `hints` sections can use input/output hidden types
953            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    /// Evaluates a `requirements` section item.
970    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    /// Evaluates a requirement in either a `requirements` section or a legacy
980    /// `runtime` section.
981    ///
982    /// Returns `true` if the name matched a requirement or `false` if it did
983    /// not.
984    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    /// Evaluates the type of a literal hints expression.
1010    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    /// Evaluates a hints item, whether in task `hints` section or a `hints`
1024    /// literal expression.
1025    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    /// Evaluates the type of a literal input expression.
1047    fn evaluate_literal_input<N: TreeNode + SyntaxNodeExt>(
1048        &mut self,
1049        expr: &LiteralInput<N>,
1050    ) -> Option<Type> {
1051        // Check to see if inputs literals are supported in the evaluation scope
1052        self.context.task()?;
1053
1054        // Evaluate the items of the literal
1055        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    /// Evaluates the type of a literal output expression.
1063    fn evaluate_literal_output<N: TreeNode + SyntaxNodeExt>(
1064        &mut self,
1065        expr: &LiteralOutput<N>,
1066    ) -> Option<Type> {
1067        // Check to see if output literals are supported in the evaluation scope
1068        self.context.task()?;
1069
1070        // Evaluate the items of the literal
1071        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    /// Evaluates a literal input/output item.
1079    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        // The first name should be an input/output and then the remainder should be a
1089        // struct member
1090        let mut span = None;
1091        let mut s: Option<&StructType> = None;
1092        while let Some((i, name)) = names.next() {
1093            // The first name is an input or an output
1094            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                // Every other name is a struct member
1124                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                    // It's ok for the last one to not name a struct
1145                }
1146            }
1147        }
1148
1149        // If we bailed out early above, calculate the entire span of the name
1150        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        // The type of every item should be `hints`
1156        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    /// Evaluates the type of an `if` expression.
1167    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        // The conditional should be a boolean
1171        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        // Check that the two expressions have the same type
1178        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    /// Evaluates the type of a `logical not` expression.
1202    fn evaluate_logical_not_expr<N: TreeNode + SyntaxNodeExt>(
1203        &mut self,
1204        expr: &LogicalNotExpr<N>,
1205    ) -> Option<Type> {
1206        // The operand should be a boolean
1207        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    /// Evaluates the type of a negation expression.
1218    fn evaluate_negation_expr<N: TreeNode + SyntaxNodeExt>(
1219        &mut self,
1220        expr: &NegationExpr<N>,
1221    ) -> Option<Type> {
1222        // The operand should be a int or float
1223        let operand = expr.operand();
1224        let ty = self.evaluate_expr(&operand)?;
1225
1226        // If the type is `Int`, treat it as `Int`
1227        // This is checked first as `Int` is coercible to `Float`
1228        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            // Type is indeterminate as the expression may evaluate to more than one type
1236            return None;
1237        }
1238
1239        Some(PrimitiveType::Float.into())
1240    }
1241
1242    /// Evaluates the type of a `logical or` expression.
1243    fn evaluate_logical_or_expr<N: TreeNode + SyntaxNodeExt>(
1244        &mut self,
1245        expr: &LogicalOrExpr<N>,
1246    ) -> Option<Type> {
1247        // Both operands should be booleans
1248        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    /// Evaluates the type of a `logical and` expression.
1266    fn evaluate_logical_and_expr<N: TreeNode + SyntaxNodeExt>(
1267        &mut self,
1268        expr: &LogicalAndExpr<N>,
1269    ) -> Option<Type> {
1270        // Both operands should be booleans
1271        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    /// Evaluates the type of a comparison expression.
1289    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        // Check for comparison to `None` or `Union` and allow it
1300        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        // Check LHS and RHS for being coercible to one of the supported primitive types
1305        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            // Only support equality/inequality comparisons for `File` and `Directory`
1314            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        // For equality comparisons, check LHS and RHS being object and compound types
1338        if op == ComparisonOperator::Equality || op == ComparisonOperator::Inequality {
1339            // Check for object
1340            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            // Check for other compound types
1348            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        // A type mismatch at this point
1374        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    /// Evaluates the type of a numeric expression.
1386    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 both sides are `Int`, the result is `Int`
1397        if lhs_ty.eq(&PrimitiveType::Integer.into()) && rhs_ty.eq(&PrimitiveType::Integer.into()) {
1398            return Some(PrimitiveType::Integer.into());
1399        }
1400
1401        // If both sides are coercible to `Float`, the result is `Float`
1402        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        // For addition, also support `String` on one or both sides of any primitive
1411        // type that isn't `Boolean`; in placeholder expressions, allow the
1412        // other side to also be optional
1413        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    /// Evaluates the type of a call expression.
1469    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                // Evaluate the argument expressions
1477                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                        // above function expect the pattern as 2nd argument
1491                        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                    // Exceeded the maximum number of arguments to any function
1574                    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    /// Evaluates the type of an index expression.
1606    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        // Determine the expected index type and result type of the expression
1613        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        // Check that the index type is the expected one
1626        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    /// Evaluates the type of an access expression.
1648    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        // Check to see if it's a compound type or call output
1666        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                // Support `left` and `right` accessors for pairs
1678                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        // Check to see if it's coercible to object; if so, treat as `Union` as it's
1700        // indeterminate
1701        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    /// Checks for unnecessary function calls.
1711    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}