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 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 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 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 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 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 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 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
211fn 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 while let Some(last) = path.segments.pop() {
241 let parent_kind = root_kind.at_path(&path);
242
243 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#[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 if path.is_root() {
396 return ctx.state_mut().insert_variable(ident.clone(), value);
397 }
398
399 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#[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 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 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 let err_type = TypeDef::from(Kind::bytes().or_null());
582 err.insert_type_def(&mut state, err_type, None);
583
584 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#[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}