vrl/compiler/expression/
assignment.rs

1use std::{convert::TryFrom, fmt};
2
3use crate::compiler::expression::function_call::FunctionCallError::InvalidArgumentKind;
4use crate::compiler::expression::function_call::InvalidArgumentErrorContext;
5use crate::compiler::{
6    CompileConfig, Context, Expression, Span, TypeDef,
7    compiler::CompilerError,
8    expression::{Expr, Resolved, assignment::ErrorVariant::InvalidParentPathSegment},
9    parser::{
10        Node,
11        ast::{self, Ident},
12    },
13    state::{TypeInfo, TypeState},
14    type_def::Details,
15    value::kind::DefaultValue,
16};
17use crate::diagnostic::{DiagnosticMessage, Label, Note};
18use crate::path::{OwnedSegment, OwnedTargetPath};
19use crate::path::{OwnedValuePath, PathPrefix};
20use crate::value::{Kind, Value};
21
22#[derive(Clone, PartialEq)]
23pub struct Assignment {
24    variant: Variant<Target, Expr>,
25}
26
27impl Assignment {
28    #[allow(clippy::too_many_lines)]
29    pub(crate) fn new(
30        node: Node<Variant<Node<ast::AssignmentTarget>, Node<Expr>>>,
31        state: &TypeState,
32        fallible_rhs: Option<&CompilerError>,
33        config: &CompileConfig,
34    ) -> Result<Self, Error> {
35        let (_, variant) = node.take();
36
37        let variant = match variant {
38            Variant::Single { target, expr } => {
39                let target_span = target.span();
40                let expr_span = expr.span();
41                let assignment_span = Span::new(target_span.start(), expr_span.start() - 1);
42                // Fallible expressions require infallible assignment.
43                if let Some(expr_error) = fallible_rhs {
44                    let assignment_error_data = match expr_error {
45                        CompilerError::FunctionCallError(InvalidArgumentKind(context)) => {
46                            AssignmentErrorData {
47                                target: target.to_string(),
48                                expression: expr.to_string(),
49                                context: Some(context.clone()),
50                            }
51                        }
52                        _ => AssignmentErrorData {
53                            target: target.to_string(),
54                            expression: expr.to_string(),
55                            context: None,
56                        },
57                    };
58
59                    return Err(Error {
60                        variant: ErrorVariant::FallibleAssignment(assignment_error_data),
61                        expr_span,
62                        assignment_span,
63                    });
64                }
65
66                // Single-target no-op assignments are useless.
67                if matches!(target.as_ref(), ast::AssignmentTarget::Noop) {
68                    return Err(Error {
69                        variant: ErrorVariant::UnnecessaryNoop(target_span),
70                        expr_span,
71                        assignment_span,
72                    });
73                }
74
75                let expr = expr.into_inner();
76                let target = Target::try_from(target.into_inner())?;
77                verify_mutable(&target, config, expr_span, assignment_span)?;
78                verify_overwritable(
79                    &target,
80                    state,
81                    target_span,
82                    expr_span,
83                    assignment_span,
84                    expr.clone(),
85                )?;
86
87                Variant::Single {
88                    target,
89                    expr: Box::new(expr),
90                }
91            }
92
93            Variant::Infallible { ok, err, expr, .. } => {
94                let ok_span = ok.span();
95                let err_span = err.span();
96                let expr_span = expr.span();
97                let assignment_span = Span::new(ok_span.start(), err_span.end());
98                let type_def = expr.type_info(state).result;
99
100                // Infallible expressions do not need fallible assignment.
101                if type_def.is_infallible() {
102                    return Err(Error {
103                        variant: ErrorVariant::InfallibleAssignment(
104                            ok.to_string(),
105                            expr.to_string(),
106                            ok_span,
107                            err_span,
108                        ),
109                        expr_span,
110                        assignment_span,
111                    });
112                }
113
114                let ok_noop = matches!(ok.as_ref(), ast::AssignmentTarget::Noop);
115                let err_noop = matches!(err.as_ref(), ast::AssignmentTarget::Noop);
116
117                // Infallible-target no-op assignments are useless.
118                if ok_noop && err_noop {
119                    return Err(Error {
120                        variant: ErrorVariant::UnnecessaryNoop(ok_span),
121                        expr_span,
122                        assignment_span,
123                    });
124                }
125
126                let expr = expr.into_inner();
127
128                // "ok" target takes on the type definition of the value, but is
129                // set to being infallible, as the error will be captured by the
130                // "err" target.
131                let ok = Target::try_from(ok.into_inner())?;
132                verify_mutable(&ok, config, expr_span, ok_span)?;
133                verify_overwritable(
134                    &ok,
135                    state,
136                    ok_span,
137                    expr_span,
138                    assignment_span,
139                    expr.clone(),
140                )?;
141
142                let type_def = type_def.infallible();
143                let default_value = type_def.default_value();
144
145                // "err" target is assigned `null` or a string containing the
146                // error message.
147                let err = Target::try_from(err.into_inner())?;
148                verify_mutable(&err, config, expr_span, err_span)?;
149                verify_overwritable(
150                    &err,
151                    state,
152                    err_span,
153                    expr_span,
154                    assignment_span,
155                    expr.clone(),
156                )?;
157
158                Variant::Infallible {
159                    ok,
160                    err,
161                    expr: Box::new(expr),
162                    default: default_value,
163                }
164            }
165        };
166
167        Ok(Self { variant })
168    }
169
170    /// Get a list of targets for this assignment.
171    ///
172    /// For regular assignments, this contains a single target, for infallible
173    /// assignments, it'll contain both the `ok` and `err` target.
174    pub(crate) fn targets(&self) -> Vec<Target> {
175        let mut targets = Vec::with_capacity(2);
176
177        match &self.variant {
178            Variant::Single { target, .. } => targets.push(target.clone()),
179            Variant::Infallible { ok, err, .. } => {
180                targets.push(ok.clone());
181                targets.push(err.clone());
182            }
183        }
184
185        targets
186    }
187}
188
189fn verify_mutable(
190    target: &Target,
191    config: &CompileConfig,
192    expr_span: Span,
193    assignment_span: Span,
194) -> Result<(), Error> {
195    match target {
196        Target::External(target_path) => {
197            if config.is_read_only_path(target_path) {
198                Err(Error {
199                    variant: ErrorVariant::ReadOnly,
200                    expr_span,
201                    assignment_span,
202                })
203            } else {
204                Ok(())
205            }
206        }
207        Target::Internal(_, _) | Target::Noop => Ok(()),
208    }
209}
210
211/// Ensure that the given target is allowed to be changed.
212///
213/// This returns an error if an assignment is done to an object field or array
214/// index, while the parent of the field/index isn't an actual object/array.
215fn verify_overwritable(
216    target: &Target,
217    state: &TypeState,
218    target_span: Span,
219    expr_span: Span,
220    assignment_span: Span,
221    rhs_expr: Expr,
222) -> Result<(), Error> {
223    let mut path = target.path();
224
225    let root_kind = match target {
226        Target::Noop => Kind::any(),
227        Target::Internal(ident, _) => state
228            .local
229            .variable(ident)
230            .map_or_else(Kind::any, |detail| detail.type_def.kind().clone()),
231        Target::External(_) => state.external.target_kind().clone(),
232    };
233
234    let mut parent_span = target_span;
235    let mut remainder_str = String::new();
236
237    // Walk the entire path from back to front. If the popped segment is a field
238    // or index, check the segment before it, and ensure that its kind is an
239    // object or array.
240    while let Some(last) = path.segments.pop() {
241        let parent_kind = root_kind.at_path(&path);
242
243        // TODO: This assumes that the Display impl of `OwnedSegment` exactly matches the
244        // VRL source code, which is not always the same. `saturating_sub` is used to guard
245        // against panics here, but the spans can be inaccurate in some cases. A different
246        // approach should be taken here
247        // https://github.com/vectordotdev/vrl/issues/206
248        let (variant, segment_span, valid) = match last {
249            segment @ OwnedSegment::Field(_) => {
250                let segment_str = segment.to_string();
251                let segment_start = parent_span.end().saturating_sub(segment_str.len());
252                let segment_span = Span::new(segment_start, parent_span.end());
253
254                parent_span = Span::new(parent_span.start(), segment_start.saturating_sub(1));
255                remainder_str.insert_str(0, &format!(".{segment_str}"));
256
257                ("object", segment_span, parent_kind.contains_object())
258            }
259            OwnedSegment::Index(index) => {
260                let segment_start = parent_span.end().saturating_sub(format!("[{index}]").len());
261                let segment_span = Span::new(segment_start, parent_span.end());
262
263                parent_span = Span::new(parent_span.start(), segment_start);
264                remainder_str.insert_str(0, &format!("[{index}]"));
265
266                ("array", segment_span, parent_kind.contains_array())
267            }
268        };
269
270        if valid {
271            continue;
272        }
273
274        let parent_str = match target {
275            Target::Internal(ident, _) => format!("{ident}{path}"),
276            Target::External(_) => {
277                if path.is_root() && remainder_str.starts_with('.') {
278                    #[allow(clippy::assigning_clones)]
279                    {
280                        remainder_str = remainder_str[1..].to_owned();
281                    }
282                }
283
284                format!(".{path}")
285            }
286            Target::Noop => unreachable!(),
287        };
288
289        return Err(Error {
290            variant: InvalidParentPathSegment {
291                variant,
292                parent_kind,
293                parent_span,
294                segment_span,
295                parent_str,
296                remainder_str,
297                rhs_expr,
298            },
299            expr_span,
300            assignment_span,
301        });
302    }
303
304    Ok(())
305}
306
307impl Expression for Assignment {
308    fn resolve(&self, ctx: &mut Context) -> Resolved {
309        self.variant.resolve(ctx)
310    }
311
312    fn type_info(&self, state: &TypeState) -> TypeInfo {
313        self.variant.type_info(state)
314    }
315}
316
317impl fmt::Display for Assignment {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        use Variant::{Infallible, Single};
320
321        match &self.variant {
322            Single { target, expr } => write!(f, "{target} = {expr}"),
323            Infallible { ok, err, expr, .. } => write!(f, "{ok}, {err} = {expr}"),
324        }
325    }
326}
327
328impl fmt::Debug for Assignment {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        use Variant::{Infallible, Single};
331
332        match &self.variant {
333            Single { target, expr } => write!(f, "{target:?} = {expr:?}"),
334            Infallible { ok, err, expr, .. } => {
335                write!(f, "Ok({ok:?}), Err({err:?}) = {expr:?}")
336            }
337        }
338    }
339}
340
341// -----------------------------------------------------------------------------
342
343#[derive(Clone, PartialEq, Eq, Hash)]
344pub(crate) enum Target {
345    Noop,
346    Internal(Ident, OwnedValuePath),
347    External(OwnedTargetPath),
348}
349
350impl Target {
351    fn insert_type_def(&self, state: &mut TypeState, new_type_def: TypeDef, value: Option<Value>) {
352        match self {
353            Self::Noop => {}
354            Self::Internal(ident, path) => {
355                let type_def = match state.local.variable(ident) {
356                    None => TypeDef::never().with_type_inserted(path, new_type_def),
357                    Some(Details { type_def, .. }) => {
358                        type_def.clone().with_type_inserted(path, new_type_def)
359                    }
360                };
361
362                let details = Details { type_def, value };
363                state.local.insert_variable(ident.clone(), details);
364            }
365
366            Self::External(target_path) => match target_path.prefix {
367                PathPrefix::Event => {
368                    state.external.update_target(Details {
369                        type_def: state
370                            .external
371                            .target()
372                            .type_def
373                            .clone()
374                            .with_type_inserted(&target_path.path, new_type_def),
375                        value,
376                    });
377                }
378                PathPrefix::Metadata => {
379                    let mut kind = state.external.metadata_kind().clone();
380                    kind.insert(&target_path.path, new_type_def.kind().clone());
381                    state.external.update_metadata(kind);
382                }
383            },
384        }
385    }
386
387    fn insert(&self, value: Value, ctx: &mut Context) {
388        use Target::{External, Internal, Noop};
389
390        match self {
391            Noop => {}
392            Internal(ident, path) => {
393                // Get the provided path, or else insert into the variable
394                // without any path appended and return early.
395                if path.is_root() {
396                    return ctx.state_mut().insert_variable(ident.clone(), value);
397                }
398
399                // Update existing variable using the provided path, or create a
400                // new value in the store.
401                match ctx.state_mut().variable_mut(ident) {
402                    Some(stored) => {
403                        stored.insert(path, value);
404                    }
405                    None => ctx
406                        .state_mut()
407                        .insert_variable(ident.clone(), value.at_path(path)),
408                }
409            }
410
411            External(path) => {
412                drop(ctx.target_mut().target_insert(path, value));
413            }
414        }
415    }
416
417    fn path(&self) -> OwnedValuePath {
418        match self {
419            Self::Noop => OwnedValuePath::root(),
420            Self::Internal(_, path) => path.clone(),
421            Self::External(target_path) => target_path.path.clone(),
422        }
423    }
424}
425
426impl fmt::Display for Target {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        use Target::{External, Internal, Noop};
429
430        match self {
431            Noop => f.write_str("_"),
432            Internal(ident, path) if path.is_root() => ident.fmt(f),
433            Internal(ident, path) => write!(f, "{ident}{path}"),
434            External(path) => write!(f, "{path}"),
435        }
436    }
437}
438
439impl fmt::Debug for Target {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        use Target::{External, Internal, Noop};
442
443        match self {
444            Noop => f.write_str("Noop"),
445            Internal(ident, path) => {
446                if path.is_root() {
447                    write!(f, "Internal({ident})")
448                } else {
449                    write!(f, "Internal({ident}{path})")
450                }
451            }
452            External(path) => write!(f, "External({path})"),
453        }
454    }
455}
456
457impl TryFrom<ast::AssignmentTarget> for Target {
458    type Error = Error;
459
460    fn try_from(target: ast::AssignmentTarget) -> Result<Self, Error> {
461        use Target::{External, Internal, Noop};
462
463        let target = match target {
464            ast::AssignmentTarget::Noop => Noop,
465            ast::AssignmentTarget::Query(query) => {
466                let ast::Query { target, path } = query;
467
468                let (target_span, target) = target.take();
469                let (path_span, path) = path.take();
470
471                let span = Span::new(target_span.start(), path_span.end());
472
473                match target {
474                    ast::QueryTarget::Internal(ident) => Internal(ident, path),
475                    ast::QueryTarget::External(prefix) => {
476                        External(OwnedTargetPath { prefix, path })
477                    }
478                    _ => {
479                        return Err(Error {
480                            variant: ErrorVariant::InvalidTarget(span),
481                            expr_span: span,
482                            assignment_span: span,
483                        });
484                    }
485                }
486            }
487            ast::AssignmentTarget::Internal(ident, path) => {
488                Internal(ident, path.unwrap_or_else(OwnedValuePath::root))
489            }
490            ast::AssignmentTarget::External(path) => {
491                External(path.unwrap_or_else(OwnedTargetPath::event_root))
492            }
493        };
494
495        Ok(target)
496    }
497}
498
499// -----------------------------------------------------------------------------
500
501#[derive(Debug, Clone, PartialEq)]
502pub(crate) enum Variant<T, U> {
503    Single {
504        target: T,
505        expr: Box<U>,
506    },
507    Infallible {
508        ok: T,
509        err: T,
510        expr: Box<U>,
511
512        /// The default `ok` value used when the expression results in an error.
513        default: Value,
514    },
515}
516
517impl<U> Expression for Variant<Target, U>
518where
519    U: Expression + Clone,
520{
521    fn resolve(&self, ctx: &mut Context) -> Resolved {
522        use Variant::{Infallible, Single};
523
524        let value = match self {
525            Single { target, expr } => {
526                let value = expr.resolve(ctx)?;
527                target.insert(value.clone(), ctx);
528                value
529            }
530            Infallible {
531                ok,
532                err,
533                expr,
534                default,
535            } => match expr.resolve(ctx) {
536                Ok(value) => {
537                    ok.insert(value.clone(), ctx);
538                    err.insert(Value::Null, ctx);
539                    value
540                }
541                Err(error) => {
542                    ok.insert(default.clone(), ctx);
543                    let value = Value::from(error.to_string());
544                    err.insert(value.clone(), ctx);
545                    value
546                }
547            },
548        };
549
550        Ok(value)
551    }
552
553    fn type_info(&self, state: &TypeState) -> TypeInfo {
554        let mut state = state.clone();
555        match &self {
556            Variant::Single { target, expr } => {
557                let expr_result = expr.apply_type_info(&mut state).impure();
558
559                let const_value = expr.resolve_constant(&state);
560                target.insert_type_def(&mut state, expr_result.clone(), const_value);
561                TypeInfo::new(state, expr_result)
562            }
563            Variant::Infallible {
564                ok,
565                err,
566                expr,
567                default,
568            } => {
569                let expr_result = expr.apply_type_info(&mut state);
570
571                // The "ok" type is either the result of the expression, or a "default" value when the expression fails.
572                let ok_type = expr_result
573                    .clone()
574                    .union(TypeDef::from(default.kind()))
575                    .infallible();
576
577                let const_value = expr.resolve_constant(&state);
578                ok.insert_type_def(&mut state, ok_type, const_value);
579
580                // The "err" type is either the error message "bytes" or "null" (not undefined).
581                let err_type = TypeDef::from(Kind::bytes().or_null());
582                err.insert_type_def(&mut state, err_type, None);
583
584                // Return type of the assignment expression itself is either the "expr" type or "bytes (the error message).
585                let assignment_result = expr_result.infallible().impure().or_bytes();
586
587                TypeInfo::new(state, assignment_result)
588            }
589        }
590    }
591}
592
593impl<T, U> fmt::Display for Variant<T, U>
594where
595    T: fmt::Display,
596    U: fmt::Display,
597{
598    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599        use Variant::{Infallible, Single};
600
601        match self {
602            Single { target, expr } => write!(f, "{target} = {expr}"),
603            Infallible { ok, err, expr, .. } => write!(f, "{ok}, {err} = {expr}"),
604        }
605    }
606}
607
608// -----------------------------------------------------------------------------
609
610#[derive(Debug)]
611pub(crate) struct Error {
612    variant: ErrorVariant,
613    expr_span: Span,
614    assignment_span: Span,
615}
616
617#[derive(Debug)]
618pub(crate) struct AssignmentErrorData {
619    target: String,
620    expression: String,
621    context: Option<InvalidArgumentErrorContext>,
622}
623
624#[derive(thiserror::Error, Debug)]
625#[allow(clippy::large_enum_variant)]
626pub(crate) enum ErrorVariant {
627    #[error("unnecessary no-op assignment")]
628    UnnecessaryNoop(Span),
629
630    #[error("unhandled fallible assignment")]
631    FallibleAssignment(AssignmentErrorData),
632
633    #[error("unnecessary error assignment")]
634    InfallibleAssignment(String, String, Span, Span),
635
636    #[error("invalid assignment target")]
637    InvalidTarget(Span),
638
639    #[error("mutation of read-only value")]
640    ReadOnly,
641
642    #[error("parent path segment rejects this mutation")]
643    InvalidParentPathSegment {
644        variant: &'static str,
645        parent_kind: Kind,
646        parent_span: Span,
647        parent_str: String,
648        segment_span: Span,
649        remainder_str: String,
650        rhs_expr: Expr,
651    },
652}
653
654impl fmt::Display for Error {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        write!(f, "{:#}", self.variant)
657    }
658}
659
660impl std::error::Error for Error {
661    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
662        Some(&self.variant)
663    }
664}
665
666impl DiagnosticMessage for Error {
667    fn code(&self) -> usize {
668        use ErrorVariant::{
669            FallibleAssignment, InfallibleAssignment, InvalidTarget, ReadOnly, UnnecessaryNoop,
670        };
671
672        match &self.variant {
673            UnnecessaryNoop(..) => 640,
674            FallibleAssignment(..) => 103,
675            InfallibleAssignment(..) => 104,
676            InvalidTarget(..) => 641,
677            InvalidParentPathSegment { .. } => 642,
678            ReadOnly => 315,
679        }
680    }
681
682    fn labels(&self) -> Vec<Label> {
683        use ErrorVariant::{
684            FallibleAssignment, InfallibleAssignment, InvalidTarget, ReadOnly, UnnecessaryNoop,
685        };
686
687        match &self.variant {
688            UnnecessaryNoop(target_span) => vec![
689                Label::primary("this no-op assignment has no effect", self.expr_span),
690                Label::context("either assign to a path or variable here", *target_span),
691                Label::context("or remove the assignment", self.assignment_span),
692            ],
693            FallibleAssignment(AssignmentErrorData {
694                target,
695                expression,
696                context,
697            }) => {
698                let mut labels = vec![Label::primary(
699                    "this expression is fallible because at least one argument's type cannot be verified to be valid",
700                    self.expr_span,
701                )];
702                if let Some(context) = context {
703                    let helper = "update the expression to be infallible by adding a `!`";
704                    if context.arguments_fmt.is_empty() {
705                        labels.push(Label::primary(helper, self.expr_span));
706                    } else {
707                        labels.push(
708                            Label::primary(format!(
709                                "`{}` argument type is `{}` and this function expected a parameter `{}` of type `{}`",
710                                context.arguments_fmt[0],
711                                context.got,
712                                context.parameter.keyword,
713                                context.parameter.kind()),
714                                           self.expr_span));
715
716                        let fixed_expression = expression.replace(
717                            context.function_ident,
718                            format!("{}!", context.function_ident).as_str(),
719                        );
720                        labels.push(Label::primary(
721                            format!("{helper}: `{fixed_expression}`"),
722                            self.expr_span,
723                        ));
724                    }
725                }
726
727                labels.extend(vec![
728                    Label::context(
729                        "or change this to an infallible assignment:",
730                        self.assignment_span,
731                    ),
732                    Label::context(
733                        format!("{target}, err = {expression}"),
734                        self.assignment_span,
735                    ),
736                ]);
737
738                labels
739            }
740            InfallibleAssignment(target, expr, ok_span, err_span) => vec![
741                Label::primary("this error assignment is unnecessary", err_span),
742                Label::context("because this expression can't fail", self.expr_span),
743                Label::context(format!("use: {target} = {expr}"), ok_span),
744            ],
745            InvalidTarget(span) => vec![
746                Label::primary("invalid assignment target", span),
747                Label::context("use one of variable or path", span),
748            ],
749            ReadOnly => vec![Label::primary(
750                "mutation of read-only value",
751                self.assignment_span,
752            )],
753            InvalidParentPathSegment {
754                variant,
755                parent_kind,
756                parent_span,
757                segment_span,
758                ..
759            } => vec![
760                Label::primary(
761                    if variant == &"object" {
762                        "querying a field of a non-object type is unsupported"
763                    } else {
764                        "indexing into a non-array type is unsupported"
765                    },
766                    segment_span,
767                ),
768                Label::context(
769                    format!("this path resolves to a value of type {parent_kind}"),
770                    parent_span,
771                ),
772            ],
773        }
774    }
775
776    fn notes(&self) -> Vec<Note> {
777        use ErrorVariant::{FallibleAssignment, InfallibleAssignment};
778
779        match &self.variant {
780            FallibleAssignment(..) => {
781                vec![Note::SeeErrorDocs, Note::SeeFunctionCharacteristicsDocs]
782            }
783            InfallibleAssignment(..) => {
784                vec![Note::SeeErrorDocs]
785            }
786            InvalidParentPathSegment {
787                variant,
788                parent_str,
789                remainder_str,
790                rhs_expr,
791                ..
792            } => {
793                let mut notes = vec![];
794
795                notes.append(&mut Note::solution(
796                    format!("change parent value to {variant}, before assignment"),
797                    if variant == &"object" {
798                        vec![
799                            format!("{parent_str} = {{}}"),
800                            format!("{parent_str}{remainder_str} = {rhs_expr}"),
801                        ]
802                    } else {
803                        vec![
804                            format!("{parent_str} = []"),
805                            format!("{parent_str}{remainder_str} = {rhs_expr}"),
806                        ]
807                    },
808                ));
809
810                notes.push(Note::SeeErrorDocs);
811
812                notes
813            }
814            _ => vec![],
815        }
816    }
817}
818
819#[cfg(test)]
820mod test {
821    use crate::compiler::state::{ExternalEnv, LocalEnv};
822    use crate::compiler::{CompileConfig, TypeState, compile_with_state};
823    use crate::value::Kind;
824
825    #[test]
826    fn never_assignment_to_target() {
827        let src = ". = abort";
828        let result = compile_with_state(
829            src,
830            &[],
831            &TypeState {
832                local: LocalEnv::default(),
833                external: ExternalEnv::new_with_kind(Kind::boolean(), Kind::integer()),
834            },
835            CompileConfig::default(),
836        )
837        .unwrap();
838        assert_eq!(
839            result
840                .program
841                .final_type_info()
842                .state
843                .external
844                .target()
845                .type_def
846                .kind(),
847            &Kind::boolean()
848        );
849    }
850
851    #[test]
852    fn never_assignment_to_metadata() {
853        let src = "% = abort";
854        let result = compile_with_state(
855            src,
856            &[],
857            &TypeState {
858                local: LocalEnv::default(),
859                external: ExternalEnv::new_with_kind(Kind::boolean(), Kind::integer()),
860            },
861            CompileConfig::default(),
862        )
863        .unwrap();
864        assert_eq!(
865            result
866                .program
867                .final_type_info()
868                .state
869                .external
870                .metadata_kind(),
871            &Kind::integer()
872        );
873    }
874
875    #[test]
876    fn never_assignment_to_new_local() {
877        let src = "foo = abort";
878        let result = compile_with_state(
879            src,
880            &[],
881            &TypeState {
882                local: LocalEnv::default(),
883                external: ExternalEnv::new_with_kind(Kind::boolean(), Kind::integer()),
884            },
885            CompileConfig::default(),
886        )
887        .unwrap();
888        assert_eq!(
889            result
890                .program
891                .final_type_info()
892                .state
893                .local
894                .variable(&"foo".to_string().into())
895                .unwrap()
896                .type_def
897                .kind(),
898            &Kind::never()
899        );
900    }
901
902    #[test]
903    fn never_assignment_to_existing_local() {
904        let src = "foo = 3; foo = abort";
905        let result = compile_with_state(
906            src,
907            &[],
908            &TypeState {
909                local: LocalEnv::default(),
910                external: ExternalEnv::new_with_kind(Kind::boolean(), Kind::integer()),
911            },
912            CompileConfig::default(),
913        )
914        .unwrap();
915        assert_eq!(
916            result
917                .program
918                .final_type_info()
919                .state
920                .local
921                .variable(&"foo".to_string().into())
922                .unwrap()
923                .type_def
924                .kind(),
925            &Kind::integer()
926        );
927    }
928}