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