sway_core/ir_generation/
const_eval.rs

1use std::{
2    io::Read,
3    ops::{BitAnd, BitOr, BitXor, Not, Rem},
4};
5
6use crate::{
7    engine_threading::*,
8    language::{
9        ty::{self, TyConstantDecl, TyIntrinsicFunctionKind},
10        CallPath, Literal,
11    },
12    metadata::MetadataManager,
13    semantic_analysis::*,
14    TypeInfo, UnifyCheck,
15};
16
17use super::{
18    convert::{convert_literal_to_constant, convert_resolved_type_id},
19    function::FnCompiler,
20    types::*,
21};
22
23use sway_ast::Intrinsic;
24use sway_error::error::CompileError;
25use sway_ir::{
26    constant::{ConstantContent, ConstantValue},
27    context::Context,
28    module::Module,
29    value::Value,
30    Constant, GlobalVar, InstOp, Instruction, Type, TypeContent,
31};
32use sway_types::{ident::Ident, integer_bits::IntegerBits, span::Spanned, Named, Span};
33use sway_utils::mapped_stack::MappedStack;
34
35#[derive(Debug)]
36enum ConstEvalError {
37    CompileError,
38    CannotBeEvaluatedToConst {
39        // This is not used at the moment because we do not give detailed description of why a
40        // const eval failed.
41        // Nonetheless, this is used in tests to help debug.
42        #[allow(dead_code)]
43        span: Span,
44    },
45}
46
47pub(crate) struct LookupEnv<'a, 'eng> {
48    pub(crate) engines: &'a Engines,
49    pub(crate) context: &'a mut Context<'eng>,
50    pub(crate) md_mgr: &'a mut MetadataManager,
51    pub(crate) module: Module,
52    pub(crate) module_ns: Option<&'a namespace::Module>,
53    pub(crate) function_compiler: Option<&'a FnCompiler<'a>>,
54    #[allow(clippy::type_complexity)]
55    pub(crate) lookup: fn(
56        &mut LookupEnv,
57        &CallPath,
58        &Option<TyConstantDecl>,
59    ) -> Result<Option<Value>, CompileError>,
60}
61
62pub(crate) fn compile_const_decl(
63    env: &mut LookupEnv,
64    call_path: &CallPath,
65    const_decl: &Option<TyConstantDecl>,
66) -> Result<Option<Value>, CompileError> {
67    // Check if it's a processed local constant.
68    if let Some(fn_compiler) = env.function_compiler {
69        let mut found_local = false;
70        if let Some(local_var) =
71            fn_compiler.get_function_var(env.context, call_path.suffix.as_str())
72        {
73            found_local = true;
74            if let Some(constant) = local_var.get_initializer(env.context) {
75                return Ok(Some(Value::new_constant(env.context, *constant)));
76            }
77
78            // Check if a constant was stored to a local variable in the current block.
79            let mut stored_const_opt: Option<&Constant> = None;
80            for ins in fn_compiler.current_block.instruction_iter(env.context) {
81                if let Some(Instruction {
82                    op:
83                        InstOp::Store {
84                            dst_val_ptr: dst_val,
85                            stored_val,
86                        },
87                    ..
88                }) = ins.get_instruction(env.context)
89                {
90                    if let Some(Instruction {
91                        op: InstOp::GetLocal(store_dst_var),
92                        ..
93                    }) = dst_val.get_instruction(env.context)
94                    {
95                        if &local_var == store_dst_var {
96                            stored_const_opt = stored_val.get_constant(env.context);
97                        }
98                    }
99                }
100            }
101            if let Some(constant) = stored_const_opt {
102                return Ok(Some(Value::new_constant(env.context, *constant)));
103            }
104        }
105
106        if let Some(value) = fn_compiler.get_function_arg(env.context, call_path.suffix.as_str()) {
107            found_local = true;
108            if value.get_constant(env.context).is_some() {
109                return Ok(Some(value));
110            }
111        }
112
113        if found_local {
114            return Ok(None);
115        }
116    }
117
118    // Check if it's a processed global constant.
119    match (
120        env.module
121            .get_global_variable(env.context, &call_path.as_vec_string()),
122        env.module_ns,
123    ) {
124        (Some(global_var), _) => {
125            let constant = global_var
126                .get_initializer(env.context)
127                .expect("const decl without initializer, should've been detected way early");
128            Ok(Some(Value::new_constant(env.context, *constant)))
129        }
130        (None, Some(module_ns)) => {
131            // See if we it's a global const and whether we can compile it *now*.
132            let decl = module_ns.root_items().check_symbol(&call_path.suffix);
133            let const_decl = match const_decl {
134                Some(decl) => Some(decl),
135                None => None,
136            };
137
138            let const_decl = match decl {
139                Ok(decl) => match decl.expect_typed() {
140                    ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id, .. }) => {
141                        Some((*env.engines.de().get_constant(&decl_id)).clone())
142                    }
143                    _otherwise => const_decl.cloned(),
144                },
145                Err(_) => const_decl.cloned(),
146            };
147
148            match const_decl {
149                Some(const_decl) => {
150                    let ty::TyConstantDecl {
151                        call_path, value, ..
152                    } = const_decl;
153
154                    let Some(value) = value else {
155                        return Ok(None);
156                    };
157
158                    let const_val = compile_constant_expression(
159                        env.engines,
160                        env.context,
161                        env.md_mgr,
162                        env.module,
163                        env.module_ns,
164                        env.function_compiler,
165                        &call_path,
166                        &value,
167                    )?;
168
169                    let const_val_c = *const_val
170                        .get_constant(env.context)
171                        .expect("Must have been compiled to a constant");
172
173                    let c_ty = const_val_c.get_content(env.context).ty;
174                    let const_global = GlobalVar::new(env.context, c_ty, Some(const_val_c), false);
175
176                    env.module.add_global_variable(
177                        env.context,
178                        call_path.as_vec_string().to_vec(),
179                        const_global,
180                    );
181
182                    Ok(Some(const_val))
183                }
184                None => Ok(None),
185            }
186        }
187        _ => Ok(None),
188    }
189}
190
191#[allow(clippy::too_many_arguments)]
192pub(super) fn compile_constant_expression(
193    engines: &Engines,
194    context: &mut Context,
195    md_mgr: &mut MetadataManager,
196    module: Module,
197    module_ns: Option<&namespace::Module>,
198    function_compiler: Option<&FnCompiler>,
199    _call_path: &CallPath,
200    const_expr: &ty::TyExpression,
201) -> Result<Value, CompileError> {
202    let span_id_idx = md_mgr.span_to_md(context, &const_expr.span);
203
204    let constant_evaluated = compile_constant_expression_to_constant(
205        engines,
206        context,
207        md_mgr,
208        module,
209        module_ns,
210        function_compiler,
211        const_expr,
212    )?;
213
214    Ok(Value::new_constant(context, constant_evaluated).add_metadatum(context, span_id_idx))
215}
216
217#[allow(clippy::too_many_arguments)]
218pub(crate) fn compile_constant_expression_to_constant(
219    engines: &Engines,
220    context: &mut Context,
221    md_mgr: &mut MetadataManager,
222    module: Module,
223    module_ns: Option<&namespace::Module>,
224    function_compiler: Option<&FnCompiler>,
225    const_expr: &ty::TyExpression,
226) -> Result<Constant, CompileError> {
227    let lookup = &mut LookupEnv {
228        engines,
229        context,
230        md_mgr,
231        module,
232        module_ns,
233        function_compiler,
234        lookup: compile_const_decl,
235    };
236
237    let err = match &const_expr.expression {
238        // Special case functions because the span in `const_expr` is to the inlined function
239        // definition, rather than the actual call site.
240        ty::TyExpressionVariant::FunctionApplication { call_path, .. } => {
241            let span = call_path.span();
242            let span = if span.is_dummy() {
243                const_expr.span.clone()
244            } else {
245                span
246            };
247            Err(CompileError::NonConstantDeclValue { span })
248        }
249        _otherwise => Err(CompileError::NonConstantDeclValue {
250            span: const_expr.span.clone(),
251        }),
252    };
253    let mut known_consts = MappedStack::<Ident, Constant>::new();
254
255    match const_eval_typed_expr(lookup, &mut known_consts, const_expr) {
256        Ok(Some(constant)) => Ok(constant),
257        Ok(None) => err,
258        Err(_) => err,
259    }
260}
261
262fn create_array_from_vec(
263    lookup: &mut LookupEnv,
264    elem_type: crate::TypeId,
265    element_types: Vec<crate::TypeId>,
266    element_vals: Vec<Constant>,
267) -> Option<Constant> {
268    let te = lookup.engines.te();
269    assert!({
270        let unify_check = UnifyCheck::coercion(lookup.engines);
271        element_types
272            .iter()
273            .all(|tid| unify_check.check(*tid, elem_type))
274    });
275
276    let arr = create_array_aggregate(
277        te,
278        lookup.engines.de(),
279        lookup.context,
280        elem_type,
281        element_types.len().try_into().unwrap(),
282    )
283    .map_or(None, |array_ty| {
284        Some(ConstantContent::new_array(
285            lookup.context,
286            array_ty.get_array_elem_type(lookup.context).unwrap(),
287            element_vals
288                .iter()
289                .map(|f| f.get_content(lookup.context).clone())
290                .collect(),
291        ))
292    });
293
294    arr.map(|c| Constant::unique(lookup.context, c))
295}
296
297/// Given an environment mapping names to constants,
298/// attempt to evaluate a typed expression to a constant.
299fn const_eval_typed_expr(
300    lookup: &mut LookupEnv,
301    known_consts: &mut MappedStack<Ident, Constant>,
302    expr: &ty::TyExpression,
303) -> Result<Option<Constant>, ConstEvalError> {
304    if let TypeInfo::ErrorRecovery(_) = &*lookup.engines.te().get(expr.return_type) {
305        return Err(ConstEvalError::CannotBeEvaluatedToConst {
306            span: expr.span.clone(),
307        });
308    }
309
310    Ok(match &expr.expression {
311        ty::TyExpressionVariant::ConstGenericExpression { decl, .. } => {
312            assert!(decl.value.is_some());
313            const_eval_typed_expr(lookup, known_consts, decl.value.as_ref().unwrap())?
314        }
315        ty::TyExpressionVariant::Literal(Literal::Numeric(n)) => {
316            let implied_lit = match &*lookup.engines.te().get(expr.return_type) {
317                TypeInfo::UnsignedInteger(IntegerBits::Eight) => Literal::U8(*n as u8),
318                _ => Literal::U64(*n),
319            };
320            Some(convert_literal_to_constant(lookup.context, &implied_lit))
321        }
322        ty::TyExpressionVariant::Literal(l) => Some(convert_literal_to_constant(lookup.context, l)),
323        ty::TyExpressionVariant::FunctionApplication {
324            arguments,
325            fn_ref,
326            call_path,
327            ..
328        } => {
329            let mut actuals_const: Vec<_> = vec![];
330
331            for arg in arguments {
332                let (name, sub_expr) = arg;
333                let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, sub_expr)?;
334                if let Some(sub_const) = eval_expr_opt {
335                    actuals_const.push((name, sub_const));
336                } else {
337                    // If all actual arguments don't evaluate a constant, bail out.
338                    // TODO: Explore if we could continue here and if it'll be useful.
339                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
340                        span: call_path.span(),
341                    });
342                }
343            }
344
345            assert!(actuals_const.len() == arguments.len());
346
347            for (name, cval) in actuals_const.into_iter() {
348                known_consts.push(name.clone(), cval);
349            }
350
351            let function_decl = lookup.engines.de().get_function(fn_ref);
352            let res = const_eval_codeblock(lookup, known_consts, &function_decl.body);
353
354            for (name, _) in arguments {
355                known_consts.pop(name);
356            }
357
358            res?
359        }
360        ty::TyExpressionVariant::ConstantExpression {
361            decl: const_decl, ..
362        } => {
363            let call_path = &const_decl.call_path;
364            let name = &call_path.suffix;
365
366            match known_consts.get(name) {
367                // 1. Check if name/call_path is in known_consts.
368                Some(cvs) => Some(*cvs),
369                None => {
370                    // 2. Check if name is a global constant.
371                    (lookup.lookup)(lookup, call_path, &Some(*const_decl.clone()))
372                        .ok()
373                        .flatten()
374                        .and_then(|v| v.get_constant(lookup.context).cloned())
375                }
376            }
377        }
378        ty::TyExpressionVariant::ConfigurableExpression { span, .. } => {
379            return Err(ConstEvalError::CannotBeEvaluatedToConst { span: span.clone() });
380        }
381        ty::TyExpressionVariant::VariableExpression {
382            name, call_path, ..
383        } => match known_consts.get(name) {
384            // 1. Check if name/call_path is in known_consts.
385            Some(cvs) => Some(*cvs),
386            None => {
387                let call_path = match call_path {
388                    Some(call_path) => call_path.clone(),
389                    None => CallPath::from(name.clone()),
390                };
391                // 2. Check if name is a global constant.
392                (lookup.lookup)(lookup, &call_path, &None)
393                    .ok()
394                    .flatten()
395                    .and_then(|v| v.get_constant(lookup.context).cloned())
396            }
397        },
398        ty::TyExpressionVariant::StructExpression {
399            fields,
400            instantiation_span,
401            ..
402        } => {
403            let (mut field_types, mut field_vals): (Vec<_>, Vec<_>) = (vec![], vec![]);
404
405            for field in fields {
406                let ty::TyStructExpressionField { name: _, value, .. } = field;
407                let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?;
408                if let Some(cv) = eval_expr_opt {
409                    field_types.push(value.return_type);
410                    field_vals.push(cv);
411                } else {
412                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
413                        span: instantiation_span.clone(),
414                    });
415                }
416            }
417
418            assert!(field_types.len() == fields.len());
419            assert!(field_vals.len() == fields.len());
420
421            get_struct_for_types(
422                lookup.engines.te(),
423                lookup.engines.de(),
424                lookup.context,
425                &field_types,
426            )
427            .map_or(None, |struct_ty| {
428                let c = ConstantContent::new_struct(
429                    lookup.context,
430                    struct_ty.get_field_types(lookup.context),
431                    field_vals
432                        .iter()
433                        .map(|fv| fv.get_content(lookup.context).clone())
434                        .collect(),
435                );
436                let c = Constant::unique(lookup.context, c);
437                Some(c)
438            })
439        }
440        ty::TyExpressionVariant::Tuple { fields } => {
441            let (mut field_types, mut field_vals): (Vec<_>, Vec<_>) = (vec![], vec![]);
442
443            for value in fields {
444                let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?;
445                if let Some(cv) = eval_expr_opt {
446                    field_types.push(value.return_type);
447                    field_vals.push(cv);
448                } else {
449                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
450                        span: expr.span.clone(),
451                    });
452                }
453            }
454
455            assert!(field_types.len() == fields.len());
456            assert!(field_vals.len() == fields.len());
457
458            create_tuple_aggregate(
459                lookup.engines.te(),
460                lookup.engines.de(),
461                lookup.context,
462                &field_types,
463            )
464            .map_or(None, |tuple_ty| {
465                let c = ConstantContent::new_struct(
466                    lookup.context,
467                    tuple_ty.get_field_types(lookup.context),
468                    field_vals
469                        .iter()
470                        .map(|fv| fv.get_content(lookup.context).clone())
471                        .collect(),
472                );
473                let c = Constant::unique(lookup.context, c);
474                Some(c)
475            })
476        }
477        ty::TyExpressionVariant::ArrayExplicit {
478            elem_type,
479            contents,
480        } => {
481            let (mut element_types, mut element_vals): (Vec<_>, Vec<_>) = (vec![], vec![]);
482
483            for value in contents {
484                let eval_expr_opt = const_eval_typed_expr(lookup, known_consts, value)?;
485                if let Some(cv) = eval_expr_opt {
486                    element_types.push(value.return_type);
487                    element_vals.push(cv);
488                } else {
489                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
490                        span: expr.span.clone(),
491                    });
492                }
493            }
494
495            assert!(element_types.len() == contents.len());
496            assert!(element_vals.len() == contents.len());
497
498            create_array_from_vec(lookup, *elem_type, element_types, element_vals)
499        }
500        ty::TyExpressionVariant::ArrayRepeat {
501            elem_type,
502            value,
503            length,
504        } => {
505            let constant = const_eval_typed_expr(lookup, known_consts, value)?.unwrap();
506            let length = const_eval_typed_expr(lookup, known_consts, length)?
507                .unwrap()
508                .get_content(lookup.context)
509                .as_uint()
510                .unwrap() as usize;
511            let element_vals = (0..length).map(|_| constant).collect::<Vec<_>>();
512            let element_types = (0..length).map(|_| value.return_type).collect::<Vec<_>>();
513
514            assert!(element_types.len() == length);
515            assert!(element_vals.len() == length);
516
517            create_array_from_vec(lookup, *elem_type, element_types, element_vals)
518        }
519        ty::TyExpressionVariant::EnumInstantiation {
520            enum_ref,
521            tag,
522            contents,
523            variant_instantiation_span,
524            ..
525        } => {
526            let enum_decl = lookup.engines.de().get_enum(enum_ref);
527            let aggregate = create_tagged_union_type(
528                lookup.engines.te(),
529                lookup.engines.de(),
530                lookup.context,
531                &enum_decl.variants,
532            );
533
534            if let Ok(enum_ty) = aggregate {
535                let tag_value = ConstantContent::new_uint(lookup.context, 64, *tag as u64);
536                let mut fields: Vec<ConstantContent> = vec![tag_value];
537
538                match contents {
539                    None => fields.push(ConstantContent::new_unit(lookup.context)),
540                    Some(subexpr) => match const_eval_typed_expr(lookup, known_consts, subexpr)? {
541                        Some(constant) => fields.push(constant.get_content(lookup.context).clone()),
542                        None => {
543                            return Err(ConstEvalError::CannotBeEvaluatedToConst {
544                                span: variant_instantiation_span.clone(),
545                            });
546                        }
547                    },
548                }
549
550                let fields_tys = enum_ty.get_field_types(lookup.context);
551                let c = ConstantContent::new_struct(lookup.context, fields_tys, fields);
552                let c = Constant::unique(lookup.context, c);
553                Some(c)
554            } else {
555                return Err(ConstEvalError::CannotBeEvaluatedToConst {
556                    span: expr.span.clone(),
557                });
558            }
559        }
560        ty::TyExpressionVariant::StructFieldAccess {
561            prefix,
562            field_to_access,
563            resolved_type_of_parent,
564            ..
565        } => match const_eval_typed_expr(lookup, known_consts, prefix)?
566            .map(|c| c.get_content(lookup.context).clone())
567        {
568            Some(ConstantContent {
569                value: ConstantValue::Struct(fields),
570                ..
571            }) => {
572                let field_kind = ty::ProjectionKind::StructField {
573                    name: field_to_access.name.clone(),
574                    field_to_access: Some(Box::new(field_to_access.clone())),
575                };
576                get_struct_name_field_index_and_type(
577                    lookup.engines.te(),
578                    lookup.engines.de(),
579                    *resolved_type_of_parent,
580                    field_kind,
581                )
582                .and_then(|(_struct_name, field_idx_and_type_opt)| {
583                    field_idx_and_type_opt.map(|(field_idx, _field_type)| field_idx)
584                })
585                .and_then(|field_idx| {
586                    fields
587                        .get(field_idx as usize)
588                        .cloned()
589                        .map(|c| Constant::unique(lookup.context, c))
590                })
591            }
592            _ => {
593                return Err(ConstEvalError::CannotBeEvaluatedToConst {
594                    span: expr.span.clone(),
595                });
596            }
597        },
598        ty::TyExpressionVariant::TupleElemAccess {
599            prefix,
600            elem_to_access_num,
601            ..
602        } => match const_eval_typed_expr(lookup, known_consts, prefix)?
603            .map(|c| c.get_content(lookup.context))
604        {
605            Some(ConstantContent {
606                value: ConstantValue::Struct(fields),
607                ..
608            }) => fields
609                .get(*elem_to_access_num)
610                .cloned()
611                .map(|c| Constant::unique(lookup.context, c)),
612            _ => {
613                return Err(ConstEvalError::CannotBeEvaluatedToConst {
614                    span: expr.span.clone(),
615                });
616            }
617        },
618        ty::TyExpressionVariant::ImplicitReturn(e) => {
619            if let Ok(Some(constant)) = const_eval_typed_expr(lookup, known_consts, e) {
620                Some(constant)
621            } else {
622                return Err(ConstEvalError::CannotBeEvaluatedToConst {
623                    span: e.span.clone(),
624                });
625            }
626        }
627        // we could allow non-local control flow in pure functions, but it would
628        // require some more work and at this point it's not clear if it is too useful
629        // for constant initializers -- the user can always refactor their pure functions
630        // to not use the return statement
631        ty::TyExpressionVariant::Return(exp) => {
632            return Err(ConstEvalError::CannotBeEvaluatedToConst {
633                span: exp.span.clone(),
634            });
635        }
636        ty::TyExpressionVariant::MatchExp { desugared, .. } => {
637            const_eval_typed_expr(lookup, known_consts, desugared)?
638        }
639        ty::TyExpressionVariant::IntrinsicFunction(kind) => {
640            const_eval_intrinsic(lookup, known_consts, kind)?
641        }
642        ty::TyExpressionVariant::IfExp {
643            condition,
644            then,
645            r#else,
646        } => {
647            match const_eval_typed_expr(lookup, known_consts, condition)?
648                .map(|c| c.get_content(lookup.context))
649            {
650                Some(ConstantContent {
651                    value: ConstantValue::Bool(cond),
652                    ..
653                }) => {
654                    if *cond {
655                        const_eval_typed_expr(lookup, known_consts, then)?
656                    } else if let Some(r#else) = r#else {
657                        const_eval_typed_expr(lookup, known_consts, r#else)?
658                    } else {
659                        // missing 'else' branch:
660                        // we probably don't really care about evaluating
661                        // const expressions of the unit type
662                        None
663                    }
664                }
665                _ => {
666                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
667                        span: expr.span.clone(),
668                    });
669                }
670            }
671        }
672        ty::TyExpressionVariant::CodeBlock(codeblock) => {
673            const_eval_codeblock(lookup, known_consts, codeblock)?
674        }
675        ty::TyExpressionVariant::ArrayIndex { prefix, index } => {
676            let prefix = const_eval_typed_expr(lookup, known_consts, prefix)?
677                .map(|c| c.get_content(lookup.context).clone());
678            let index = const_eval_typed_expr(lookup, known_consts, index)?
679                .map(|c| c.get_content(lookup.context));
680            match (prefix, index) {
681                (
682                    Some(ConstantContent {
683                        value: ConstantValue::Array(items),
684                        ..
685                    }),
686                    Some(ConstantContent {
687                        value: ConstantValue::Uint(index),
688                        ..
689                    }),
690                ) => {
691                    let count = items.len() as u64;
692                    if *index < count {
693                        let c = Constant::unique(lookup.context, items[*index as usize].clone());
694                        Some(c)
695                    } else {
696                        return Err(ConstEvalError::CompileError);
697                    }
698                }
699                _ => {
700                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
701                        span: expr.span.clone(),
702                    });
703                }
704            }
705        }
706        ty::TyExpressionVariant::Ref(_) => {
707            return Err(ConstEvalError::CompileError);
708        }
709        // We support *__elem_at(...)
710        ty::TyExpressionVariant::Deref(expr) => {
711            let value = expr
712                .as_intrinsic()
713                .filter(|x| matches!(x.kind, Intrinsic::ElemAt))
714                .ok_or(ConstEvalError::CompileError)
715                .and_then(|kind| {
716                    const_eval_intrinsic(lookup, known_consts, kind)
717                        .map(|c| c.map(|c| c.get_content(lookup.context).clone()))
718                });
719            if let Ok(Some(ConstantContent {
720                value: ConstantValue::Reference(value),
721                ..
722            })) = value
723            {
724                let c = Constant::unique(lookup.context, *value.clone());
725                Some(c)
726            } else {
727                return Err(ConstEvalError::CompileError);
728            }
729        }
730        ty::TyExpressionVariant::EnumTag { exp } => {
731            let value = const_eval_typed_expr(lookup, known_consts, exp)?
732                .map(|x| x.get_content(lookup.context).value.clone());
733            if let Some(ConstantValue::Struct(fields)) = value {
734                Some(Constant::unique(lookup.context, fields[0].clone()))
735            } else {
736                return Err(ConstEvalError::CompileError);
737            }
738        }
739        ty::TyExpressionVariant::UnsafeDowncast { exp, .. } => {
740            let value = const_eval_typed_expr(lookup, known_consts, exp)?
741                .map(|x| x.get_content(lookup.context).value.clone());
742            if let Some(ConstantValue::Struct(fields)) = value {
743                Some(Constant::unique(lookup.context, fields[1].clone()))
744            } else {
745                return Err(ConstEvalError::CompileError);
746            }
747        }
748        ty::TyExpressionVariant::WhileLoop {
749            condition, body, ..
750        } => {
751            // Arbitrary limit of iterations to avoid infinite loops like
752            // while true {}
753            let mut limit = 1_000_000;
754
755            while limit >= 0 {
756                limit -= 1;
757
758                let condition = const_eval_typed_expr(lookup, known_consts, condition)?;
759                match condition.map(|x| x.get_content(lookup.context).value.clone()) {
760                    Some(ConstantValue::Bool(true)) => {
761                        // Break and continue are not implemented, so there is need for flow control here
762                        let _ = const_eval_codeblock(lookup, known_consts, body)?;
763                    }
764                    _ => break,
765                }
766            }
767
768            None
769        }
770        ty::TyExpressionVariant::Reassignment(r) => {
771            let rhs = const_eval_typed_expr(lookup, known_consts, &r.rhs)?.unwrap();
772            match &r.lhs {
773                ty::TyReassignmentTarget::ElementAccess {
774                    base_name, indices, ..
775                } => {
776                    if !indices.is_empty() {
777                        return Err(ConstEvalError::CannotBeEvaluatedToConst {
778                            span: expr.span.clone(),
779                        });
780                    }
781                    if let Some(lhs) = known_consts.get_mut(base_name) {
782                        *lhs = rhs;
783                        return Ok(None);
784                    } else {
785                        return Err(ConstEvalError::CannotBeEvaluatedToConst {
786                            span: expr.span.clone(),
787                        });
788                    }
789                }
790                ty::TyReassignmentTarget::DerefAccess { .. } => {
791                    return Err(ConstEvalError::CannotBeEvaluatedToConst {
792                        span: expr.span.clone(),
793                    });
794                }
795            }
796        }
797        ty::TyExpressionVariant::FunctionParameter
798        | ty::TyExpressionVariant::AsmExpression { .. }
799        | ty::TyExpressionVariant::LazyOperator { .. }
800        | ty::TyExpressionVariant::AbiCast { .. }
801        | ty::TyExpressionVariant::StorageAccess(_)
802        | ty::TyExpressionVariant::AbiName(_)
803        | ty::TyExpressionVariant::Break
804        | ty::TyExpressionVariant::Continue
805        | ty::TyExpressionVariant::ForLoop { .. } => {
806            return Err(ConstEvalError::CannotBeEvaluatedToConst {
807                span: expr.span.clone(),
808            });
809        }
810    })
811}
812
813// the (constant) value of a codeblock is essentially it's last expression if there is one
814// or if it makes sense as the last expression, e.g. a dangling let-expression in a codeblock
815// would be an evaluation error
816fn const_eval_codeblock(
817    lookup: &mut LookupEnv,
818    known_consts: &mut MappedStack<Ident, Constant>,
819    codeblock: &ty::TyCodeBlock,
820) -> Result<Option<Constant>, ConstEvalError> {
821    // the current result
822    let mut result: Result<Option<Constant>, ConstEvalError> = Ok(None);
823    // keep track of new bindings for this codeblock
824    let mut bindings: Vec<_> = vec![];
825
826    for ast_node in &codeblock.contents {
827        result = match &ast_node.content {
828            ty::TyAstNodeContent::Declaration(decl @ ty::TyDecl::VariableDecl(var_decl)) => {
829                if let Ok(Some(rhs)) = const_eval_typed_expr(lookup, known_consts, &var_decl.body) {
830                    known_consts.push(var_decl.name.clone(), rhs);
831                    bindings.push(var_decl.name.clone());
832                    Ok(None)
833                } else {
834                    Err(ConstEvalError::CannotBeEvaluatedToConst {
835                        span: decl.span(lookup.engines).clone(),
836                    })
837                }
838            }
839            ty::TyAstNodeContent::Declaration(ty::TyDecl::ConstantDecl(const_decl)) => {
840                let ty_const_decl = lookup.engines.de().get_constant(&const_decl.decl_id);
841                if let Some(constant) = ty_const_decl
842                    .value
843                    .clone()
844                    .and_then(|expr| const_eval_typed_expr(lookup, known_consts, &expr).ok())
845                    .flatten()
846                {
847                    known_consts.push(ty_const_decl.name().clone(), constant);
848                    bindings.push(ty_const_decl.name().clone());
849                    Ok(None)
850                } else {
851                    Err(ConstEvalError::CannotBeEvaluatedToConst {
852                        span: ty_const_decl.span.clone(),
853                    })
854                }
855            }
856            ty::TyAstNodeContent::Declaration(_) => Ok(None),
857            ty::TyAstNodeContent::Expression(e) => match e.expression {
858                ty::TyExpressionVariant::ImplicitReturn(_) => {
859                    if let Ok(Some(constant)) = const_eval_typed_expr(lookup, known_consts, e) {
860                        Ok(Some(constant))
861                    } else {
862                        Err(ConstEvalError::CannotBeEvaluatedToConst {
863                            span: e.span.clone(),
864                        })
865                    }
866                }
867                _ => {
868                    if const_eval_typed_expr(lookup, known_consts, e).is_err() {
869                        Err(ConstEvalError::CannotBeEvaluatedToConst {
870                            span: e.span.clone(),
871                        })
872                    } else {
873                        Ok(None)
874                    }
875                }
876            },
877            ty::TyAstNodeContent::SideEffect(_) => Err(ConstEvalError::CannotBeEvaluatedToConst {
878                span: ast_node.span.clone(),
879            }),
880            ty::TyAstNodeContent::Error(_, _) => Err(ConstEvalError::CannotBeEvaluatedToConst {
881                span: ast_node.span.clone(),
882            }),
883        };
884
885        if result.is_err() {
886            break;
887        }
888    }
889
890    // remove introduced vars/consts from scope at the end of the codeblock
891    for name in bindings {
892        known_consts.pop(&name)
893    }
894
895    result
896}
897
898fn as_encode_buffer<'a>(context: &'a Context, buffer: &Constant) -> Option<(&'a Vec<u8>, u64)> {
899    match &buffer.get_content(context).value {
900        ConstantValue::Struct(fields) => {
901            let slice = match &fields[0].value {
902                ConstantValue::RawUntypedSlice(bytes) => bytes,
903                _ => return None,
904            };
905            let len = match fields[1].value {
906                ConstantValue::Uint(v) => v,
907                _ => return None,
908            };
909            Some((slice, len))
910        }
911        _ => None,
912    }
913}
914
915fn to_encode_buffer(lookup: &mut LookupEnv, bytes: Vec<u8>, len: u64) -> Constant {
916    let c = ConstantContent {
917        ty: Type::new_struct(
918            lookup.context,
919            vec![
920                Type::get_slice(lookup.context),
921                Type::get_uint64(lookup.context),
922            ],
923        ),
924        value: ConstantValue::Struct(vec![
925            ConstantContent {
926                ty: Type::get_slice(lookup.context),
927                value: ConstantValue::RawUntypedSlice(bytes),
928            },
929            ConstantContent {
930                ty: Type::get_uint64(lookup.context),
931                value: ConstantValue::Uint(len),
932            },
933        ]),
934    };
935    Constant::unique(lookup.context, c)
936}
937
938fn const_eval_intrinsic(
939    lookup: &mut LookupEnv,
940    known_consts: &mut MappedStack<Ident, Constant>,
941    intrinsic: &TyIntrinsicFunctionKind,
942) -> Result<Option<Constant>, ConstEvalError> {
943    let mut args = vec![];
944    for arg in intrinsic.arguments.iter() {
945        if let Ok(Some(constant)) = const_eval_typed_expr(lookup, known_consts, arg) {
946            args.push(constant);
947        } else {
948            return Err(ConstEvalError::CannotBeEvaluatedToConst {
949                span: arg.span.clone(),
950            });
951        }
952    }
953
954    assert!(args.len() == intrinsic.arguments.len());
955
956    match intrinsic.kind {
957        Intrinsic::Add | Intrinsic::Sub | Intrinsic::Mul | Intrinsic::Div | Intrinsic::Mod => {
958            let ty = args[0].get_content(lookup.context).ty;
959            assert!(
960                args.len() == 2 && ty.eq(lookup.context, &args[1].get_content(lookup.context).ty)
961            );
962
963            use ConstantValue::*;
964            let c = match (
965                &args[0].get_content(lookup.context).value,
966                &args[1].get_content(lookup.context).value,
967            ) {
968                (Uint(arg1), Uint(ref arg2)) => {
969                    // All arithmetic is done as if it were u64
970                    let result = match intrinsic.kind {
971                        Intrinsic::Add => arg1.checked_add(*arg2),
972                        Intrinsic::Sub => arg1.checked_sub(*arg2),
973                        Intrinsic::Mul => arg1.checked_mul(*arg2),
974                        Intrinsic::Div => arg1.checked_div(*arg2),
975                        Intrinsic::Mod => arg1.checked_rem(*arg2),
976                        _ => unreachable!(),
977                    };
978
979                    match result {
980                        Some(result) => Ok(Some(ConstantContent {
981                            ty,
982                            value: ConstantValue::Uint(result),
983                        })),
984                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
985                            span: intrinsic.span.clone(),
986                        }),
987                    }
988                }
989                (U256(arg1), U256(arg2)) => {
990                    let result = match intrinsic.kind {
991                        Intrinsic::Add => arg1.checked_add(arg2),
992                        Intrinsic::Sub => arg1.checked_sub(arg2),
993                        Intrinsic::Mul => arg1.checked_mul(arg2),
994                        Intrinsic::Div => arg1.checked_div(arg2),
995                        Intrinsic::Mod => Some(arg1.rem(arg2)),
996                        _ => unreachable!(),
997                    };
998
999                    match result {
1000                        Some(result) => Ok(Some(ConstantContent {
1001                            ty,
1002                            value: ConstantValue::U256(result),
1003                        })),
1004                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1005                            span: intrinsic.span.clone(),
1006                        }),
1007                    }
1008                }
1009                _ => {
1010                    panic!("Type checker allowed incorrect args to binary op");
1011                }
1012            };
1013            c.map(|c| c.map(|c| Constant::unique(lookup.context, c)))
1014        }
1015        Intrinsic::And | Intrinsic::Or | Intrinsic::Xor => {
1016            let ty = args[0].get_content(lookup.context).ty;
1017            assert!(
1018                args.len() == 2 && ty.eq(lookup.context, &args[1].get_content(lookup.context).ty)
1019            );
1020
1021            use ConstantValue::*;
1022            let c = match (
1023                &args[0].get_content(lookup.context).value,
1024                &args[1].get_content(lookup.context).value,
1025            ) {
1026                (Uint(arg1), Uint(ref arg2)) => {
1027                    // All arithmetic is done as if it were u64
1028                    let result = match intrinsic.kind {
1029                        Intrinsic::And => Some(arg1.bitand(arg2)),
1030                        Intrinsic::Or => Some(arg1.bitor(*arg2)),
1031                        Intrinsic::Xor => Some(arg1.bitxor(*arg2)),
1032                        _ => unreachable!(),
1033                    };
1034
1035                    match result {
1036                        Some(sum) => Ok(Some(ConstantContent {
1037                            ty,
1038                            value: ConstantValue::Uint(sum),
1039                        })),
1040                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1041                            span: intrinsic.span.clone(),
1042                        }),
1043                    }
1044                }
1045                (U256(arg1), U256(arg2)) => {
1046                    let result = match intrinsic.kind {
1047                        Intrinsic::And => Some(arg1.bitand(arg2)),
1048                        Intrinsic::Or => Some(arg1.bitor(arg2)),
1049                        Intrinsic::Xor => Some(arg1.bitxor(arg2)),
1050                        _ => unreachable!(),
1051                    };
1052
1053                    match result {
1054                        Some(sum) => Ok(Some(ConstantContent {
1055                            ty,
1056                            value: ConstantValue::U256(sum),
1057                        })),
1058                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1059                            span: intrinsic.span.clone(),
1060                        }),
1061                    }
1062                }
1063                (B256(arg1), B256(arg2)) => {
1064                    let result = match intrinsic.kind {
1065                        Intrinsic::And => Some(arg1.bitand(arg2)),
1066                        Intrinsic::Or => Some(arg1.bitor(arg2)),
1067                        Intrinsic::Xor => Some(arg1.bitxor(arg2)),
1068                        _ => unreachable!(),
1069                    };
1070
1071                    match result {
1072                        Some(result) => Ok(Some(ConstantContent {
1073                            ty,
1074                            value: ConstantValue::B256(result),
1075                        })),
1076                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1077                            span: intrinsic.span.clone(),
1078                        }),
1079                    }
1080                }
1081                _ => {
1082                    panic!("Type checker allowed incorrect args to binary op");
1083                }
1084            };
1085            c.map(|c| c.map(|c| Constant::unique(lookup.context, c)))
1086        }
1087        Intrinsic::Lsh | Intrinsic::Rsh => {
1088            assert!(args.len() == 2);
1089            assert!(
1090                args[0]
1091                    .get_content(lookup.context)
1092                    .ty
1093                    .is_uint(lookup.context)
1094                    || args[0]
1095                        .get_content(lookup.context)
1096                        .ty
1097                        .is_b256(lookup.context)
1098            );
1099            assert!(args[1]
1100                .get_content(lookup.context)
1101                .ty
1102                .is_uint64(lookup.context));
1103
1104            let ty = args[0].get_content(lookup.context).ty;
1105
1106            use ConstantValue::*;
1107            let c = match (
1108                &args[0].get_content(lookup.context).value,
1109                &args[1].get_content(lookup.context).value,
1110            ) {
1111                (Uint(arg1), Uint(ref arg2)) => {
1112                    let result = match intrinsic.kind {
1113                        Intrinsic::Lsh => u32::try_from(*arg2)
1114                            .ok()
1115                            .and_then(|arg2| arg1.checked_shl(arg2)),
1116                        Intrinsic::Rsh => u32::try_from(*arg2)
1117                            .ok()
1118                            .and_then(|arg2| arg1.checked_shr(arg2)),
1119                        _ => unreachable!(),
1120                    };
1121
1122                    match result {
1123                        Some(sum) => Ok(Some(ConstantContent {
1124                            ty,
1125                            value: ConstantValue::Uint(sum),
1126                        })),
1127                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1128                            span: intrinsic.span.clone(),
1129                        }),
1130                    }
1131                }
1132                (U256(arg1), Uint(ref arg2)) => {
1133                    let result = match intrinsic.kind {
1134                        Intrinsic::Lsh => arg1.checked_shl(arg2),
1135                        Intrinsic::Rsh => Some(arg1.shr(arg2)),
1136                        _ => unreachable!(),
1137                    };
1138
1139                    match result {
1140                        Some(value) => Ok(Some(ConstantContent {
1141                            ty,
1142                            value: ConstantValue::U256(value),
1143                        })),
1144                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1145                            span: intrinsic.span.clone(),
1146                        }),
1147                    }
1148                }
1149                (B256(arg1), Uint(ref arg2)) => {
1150                    let result = match intrinsic.kind {
1151                        Intrinsic::Lsh => arg1.checked_shl(arg2),
1152                        Intrinsic::Rsh => Some(arg1.shr(arg2)),
1153                        _ => unreachable!(),
1154                    };
1155
1156                    match result {
1157                        Some(result) => Ok(Some(ConstantContent {
1158                            ty,
1159                            value: ConstantValue::B256(result),
1160                        })),
1161                        None => Err(ConstEvalError::CannotBeEvaluatedToConst {
1162                            span: intrinsic.span.clone(),
1163                        }),
1164                    }
1165                }
1166                _ => {
1167                    panic!("Type checker allowed incorrect args to binary op");
1168                }
1169            };
1170            c.map(|c| c.map(|c| Constant::unique(lookup.context, c)))
1171        }
1172        Intrinsic::SizeOfType => {
1173            let targ = &intrinsic.type_arguments[0];
1174            let ir_type = convert_resolved_type_id(
1175                lookup.engines.te(),
1176                lookup.engines.de(),
1177                lookup.context,
1178                targ.type_id(),
1179                &targ.span(),
1180            )
1181            .map_err(|_| ConstEvalError::CompileError)?;
1182            let c = ConstantContent {
1183                ty: Type::get_uint64(lookup.context),
1184                value: ConstantValue::Uint(ir_type.size(lookup.context).in_bytes()),
1185            };
1186
1187            Ok(Some(Constant::unique(lookup.context, c)))
1188        }
1189        Intrinsic::SizeOfVal => {
1190            let val = &intrinsic.arguments[0];
1191            let type_id = val.return_type;
1192            let ir_type = convert_resolved_type_id(
1193                lookup.engines.te(),
1194                lookup.engines.de(),
1195                lookup.context,
1196                type_id,
1197                &val.span,
1198            )
1199            .map_err(|_| ConstEvalError::CompileError)?;
1200            let c = ConstantContent {
1201                ty: Type::get_uint64(lookup.context),
1202                value: ConstantValue::Uint(ir_type.size(lookup.context).in_bytes()),
1203            };
1204            Ok(Some(Constant::unique(lookup.context, c)))
1205        }
1206        Intrinsic::SizeOfStr => {
1207            let targ = &intrinsic.type_arguments[0];
1208            let ir_type = convert_resolved_type_id(
1209                lookup.engines.te(),
1210                lookup.engines.de(),
1211                lookup.context,
1212                targ.type_id(),
1213                &targ.span(),
1214            )
1215            .map_err(|_| ConstEvalError::CompileError)?;
1216            let c = ConstantContent {
1217                ty: Type::get_uint64(lookup.context),
1218                value: ConstantValue::Uint(
1219                    ir_type.get_string_len(lookup.context).unwrap_or_default(),
1220                ),
1221            };
1222            Ok(Some(Constant::unique(lookup.context, c)))
1223        }
1224        Intrinsic::AssertIsStrArray => {
1225            let targ = &intrinsic.type_arguments[0];
1226            let ir_type = convert_resolved_type_id(
1227                lookup.engines.te(),
1228                lookup.engines.de(),
1229                lookup.context,
1230                targ.type_id(),
1231                &targ.span(),
1232            )
1233            .map_err(|_| ConstEvalError::CompileError)?;
1234            match ir_type.get_content(lookup.context) {
1235                TypeContent::StringSlice | TypeContent::StringArray(_) => {
1236                    let c = ConstantContent {
1237                        ty: Type::get_unit(lookup.context),
1238                        value: ConstantValue::Unit,
1239                    };
1240                    Ok(Some(Constant::unique(lookup.context, c)))
1241                }
1242                _ => Err(ConstEvalError::CompileError),
1243            }
1244        }
1245        Intrinsic::ToStrArray => {
1246            assert!(args.len() == 1);
1247            match &args[0].get_content(lookup.context).value {
1248                ConstantValue::String(s) => {
1249                    let c = ConstantContent::new_string(lookup.context, s.to_vec());
1250                    Ok(Some(Constant::unique(lookup.context, c)))
1251                }
1252                _ => {
1253                    unreachable!("Type checker allowed non string value for ToStrArray")
1254                }
1255            }
1256        }
1257        Intrinsic::Eq => {
1258            assert!(args.len() == 2);
1259            let c = ConstantContent {
1260                ty: Type::get_bool(lookup.context),
1261                value: ConstantValue::Bool(args[0] == args[1]),
1262            };
1263            Ok(Some(Constant::unique(lookup.context, c)))
1264        }
1265        Intrinsic::Gt => match (
1266            &args[0].get_content(lookup.context).value,
1267            &args[1].get_content(lookup.context).value,
1268        ) {
1269            (ConstantValue::Uint(val1), ConstantValue::Uint(val2)) => {
1270                let c = ConstantContent {
1271                    ty: Type::get_bool(lookup.context),
1272                    value: ConstantValue::Bool(val1 > val2),
1273                };
1274                Ok(Some(Constant::unique(lookup.context, c)))
1275            }
1276            (ConstantValue::U256(val1), ConstantValue::U256(val2)) => {
1277                let c = ConstantContent {
1278                    ty: Type::get_bool(lookup.context),
1279                    value: ConstantValue::Bool(val1 > val2),
1280                };
1281                Ok(Some(Constant::unique(lookup.context, c)))
1282            }
1283            _ => {
1284                unreachable!("Type checker allowed non integer value for GreaterThan")
1285            }
1286        },
1287        Intrinsic::Lt => match (
1288            &args[0].get_content(lookup.context).value,
1289            &args[1].get_content(lookup.context).value,
1290        ) {
1291            (ConstantValue::Uint(val1), ConstantValue::Uint(val2)) => {
1292                let c = ConstantContent {
1293                    ty: Type::get_bool(lookup.context),
1294                    value: ConstantValue::Bool(val1 < val2),
1295                };
1296                Ok(Some(Constant::unique(lookup.context, c)))
1297            }
1298            (ConstantValue::U256(val1), ConstantValue::U256(val2)) => {
1299                let c = ConstantContent {
1300                    ty: Type::get_bool(lookup.context),
1301                    value: ConstantValue::Bool(val1 < val2),
1302                };
1303                Ok(Some(Constant::unique(lookup.context, c)))
1304            }
1305            _ => {
1306                unreachable!("Type checker allowed non integer value for LessThan")
1307            }
1308        },
1309        Intrinsic::AddrOf
1310        | Intrinsic::PtrAdd
1311        | Intrinsic::PtrSub
1312        | Intrinsic::IsReferenceType
1313        | Intrinsic::IsStrArray
1314        | Intrinsic::Gtf
1315        | Intrinsic::StateClear
1316        | Intrinsic::StateLoadWord
1317        | Intrinsic::StateStoreWord
1318        | Intrinsic::StateLoadQuad
1319        | Intrinsic::StateStoreQuad
1320        | Intrinsic::Log
1321        | Intrinsic::Revert
1322        | Intrinsic::JmpMem
1323        | Intrinsic::Smo => Err(ConstEvalError::CannotBeEvaluatedToConst {
1324            span: intrinsic.span.clone(),
1325        }),
1326        Intrinsic::Not => {
1327            // `not` works only with uint/u256/b256 at the moment
1328            // `bool` ops::Not implementation uses `__eq`.
1329
1330            assert!(args.len() == 1);
1331            assert!(
1332                args[0]
1333                    .get_content(lookup.context)
1334                    .ty
1335                    .is_uint(lookup.context)
1336                    || args[0]
1337                        .get_content(lookup.context)
1338                        .ty
1339                        .is_b256(lookup.context)
1340            );
1341
1342            let Some(arg) = args.into_iter().next() else {
1343                unreachable!("Unexpected 'not' without any arguments");
1344            };
1345
1346            let c = match &arg.get_content(lookup.context).value {
1347                ConstantValue::Uint(n) => {
1348                    let n = match arg
1349                        .get_content(lookup.context)
1350                        .ty
1351                        .get_uint_width(lookup.context)
1352                    {
1353                        Some(8) => !(*n as u8) as u64,
1354                        Some(16) => !(*n as u16) as u64,
1355                        Some(32) => !(*n as u32) as u64,
1356                        Some(64) => !n,
1357                        _ => unreachable!("Invalid unsigned integer width"),
1358                    };
1359                    Ok(Some(ConstantContent {
1360                        ty: arg.get_content(lookup.context).ty,
1361                        value: ConstantValue::Uint(n),
1362                    }))
1363                }
1364                ConstantValue::U256(n) => Ok(Some(ConstantContent {
1365                    ty: arg.get_content(lookup.context).ty,
1366                    value: ConstantValue::U256(n.not()),
1367                })),
1368                ConstantValue::B256(v) => Ok(Some(ConstantContent {
1369                    ty: arg.get_content(lookup.context).ty,
1370                    value: ConstantValue::B256(v.not()),
1371                })),
1372                _ => {
1373                    unreachable!("Type checker allowed non integer value for Not");
1374                }
1375            };
1376            c.map(|c| c.map(|c| Constant::unique(lookup.context, c)))
1377        }
1378        Intrinsic::ContractCall | Intrinsic::ContractRet => {
1379            Err(ConstEvalError::CannotBeEvaluatedToConst {
1380                span: intrinsic.span.clone(),
1381            })
1382        }
1383        Intrinsic::EncodeBufferEmpty => Ok(Some(to_encode_buffer(lookup, vec![], 0))),
1384        Intrinsic::EncodeBufferAppend => {
1385            assert!(args.len() == 2);
1386
1387            let (slice, mut len) = as_encode_buffer(lookup.context, &args[0]).unwrap();
1388            let mut bytes = slice.clone();
1389
1390            use ConstantValue::*;
1391            match &args[1].get_content(lookup.context).value {
1392                Bool(v) => {
1393                    bytes.extend(if *v { [1] } else { [0] });
1394                    len += 1;
1395                    Ok(Some(to_encode_buffer(lookup, bytes, len)))
1396                }
1397                Uint(v) => {
1398                    match &*lookup.engines.te().get(intrinsic.arguments[1].return_type) {
1399                        TypeInfo::UnsignedInteger(IntegerBits::Eight) => {
1400                            bytes.extend((*v as u8).to_be_bytes());
1401                            len += 1;
1402                        }
1403                        TypeInfo::UnsignedInteger(IntegerBits::Sixteen) => {
1404                            bytes.extend((*v as u16).to_be_bytes());
1405                            len += 2;
1406                        }
1407                        TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo) => {
1408                            bytes.extend((*v as u32).to_be_bytes());
1409                            len += 4;
1410                        }
1411                        TypeInfo::UnsignedInteger(IntegerBits::SixtyFour) => {
1412                            bytes.extend(v.to_be_bytes());
1413                            len += 8;
1414                        }
1415                        _ => {
1416                            return Err(ConstEvalError::CannotBeEvaluatedToConst {
1417                                span: intrinsic.span.clone(),
1418                            });
1419                        }
1420                    };
1421                    Ok(Some(to_encode_buffer(lookup, bytes, len)))
1422                }
1423                U256(v) => {
1424                    bytes.extend(v.to_be_bytes());
1425                    len += 32;
1426                    Ok(Some(to_encode_buffer(lookup, bytes, len)))
1427                }
1428                B256(v) => {
1429                    bytes.extend(v.to_be_bytes());
1430                    len += 32;
1431                    Ok(Some(to_encode_buffer(lookup, bytes, len)))
1432                }
1433                String(v) => {
1434                    if let TypeInfo::StringSlice =
1435                        &*lookup.engines.te().get(intrinsic.arguments[1].return_type)
1436                    {
1437                        let l = v.len() as u64;
1438                        bytes.extend(l.to_be_bytes());
1439                        len += 8;
1440                    }
1441
1442                    bytes.extend(v);
1443                    len += v.len() as u64;
1444
1445                    Ok(Some(to_encode_buffer(lookup, bytes, len)))
1446                }
1447                _ => Err(ConstEvalError::CannotBeEvaluatedToConst {
1448                    span: intrinsic.span.clone(),
1449                }),
1450            }
1451        }
1452        Intrinsic::EncodeBufferAsRawSlice => {
1453            assert!(args.len() == 1);
1454
1455            let (slice, len) = as_encode_buffer(lookup.context, &args[0]).unwrap();
1456            let bytes = slice.clone();
1457
1458            let c = ConstantContent {
1459                ty: Type::get_slice(lookup.context),
1460                value: ConstantValue::RawUntypedSlice(bytes[0..(len as usize)].to_vec()),
1461            };
1462            Ok(Some(Constant::unique(lookup.context, c)))
1463        }
1464        Intrinsic::Slice => {
1465            let start = args[1]
1466                .get_content(lookup.context)
1467                .as_uint()
1468                .expect("Type check allowed non u64") as usize;
1469            let end = args[2]
1470                .get_content(lookup.context)
1471                .as_uint()
1472                .expect("Type check allowed non u64") as usize;
1473
1474            match &args[0].get_content(lookup.context).value {
1475                ConstantValue::Array(elements) => {
1476                    let slice = elements
1477                        .get(start..end)
1478                        .ok_or(ConstEvalError::CompileError)?;
1479                    let elem_type = args[0]
1480                        .get_content(lookup.context)
1481                        .ty
1482                        .get_array_elem_type(lookup.context)
1483                        .expect("unexpected non array");
1484                    let s = slice.to_vec();
1485                    let c = ConstantContent {
1486                        ty: Type::get_typed_slice(lookup.context, elem_type),
1487                        value: ConstantValue::Slice(s),
1488                    };
1489                    Ok(Some(Constant::unique(lookup.context, c)))
1490                }
1491                ConstantValue::Reference(r) => match &r.value {
1492                    ConstantValue::Slice(elements) => {
1493                        let slice = elements
1494                            .get(start..end)
1495                            .ok_or(ConstEvalError::CompileError)?;
1496                        let elem_type = args[0]
1497                            .get_content(lookup.context)
1498                            .ty
1499                            .get_typed_slice_elem_type(lookup.context)
1500                            .expect("unexpected non slice");
1501                        let s = slice.to_vec();
1502                        let c = ConstantContent {
1503                            ty: Type::get_typed_slice(lookup.context, elem_type),
1504                            value: ConstantValue::Slice(s),
1505                        };
1506                        Ok(Some(Constant::unique(lookup.context, c)))
1507                    }
1508                    _ => Err(ConstEvalError::CannotBeEvaluatedToConst {
1509                        span: intrinsic.span.clone(),
1510                    }),
1511                },
1512                _ => Err(ConstEvalError::CannotBeEvaluatedToConst {
1513                    span: intrinsic.span.clone(),
1514                }),
1515            }
1516        }
1517        Intrinsic::ElemAt => {
1518            let idx = args[1]
1519                .get_content(lookup.context)
1520                .as_uint()
1521                .expect("Type check allowed non u64") as usize;
1522
1523            match &args[0].get_content(lookup.context).value {
1524                ConstantValue::Reference(r) => match &r.value {
1525                    ConstantValue::Slice(elements) => {
1526                        let v = elements[idx].clone();
1527                        let c = ConstantContent {
1528                            ty: Type::new_ptr(lookup.context, v.ty),
1529                            value: ConstantValue::Reference(Box::new(v)),
1530                        };
1531                        Ok(Some(Constant::unique(lookup.context, c)))
1532                    }
1533                    _ => Err(ConstEvalError::CannotBeEvaluatedToConst {
1534                        span: intrinsic.span.clone(),
1535                    }),
1536                },
1537                _ => Err(ConstEvalError::CannotBeEvaluatedToConst {
1538                    span: intrinsic.span.clone(),
1539                }),
1540            }
1541        }
1542        Intrinsic::Transmute => {
1543            let src_type = &intrinsic.type_arguments[0];
1544            let src_ir_type = convert_resolved_type_id(
1545                lookup.engines.te(),
1546                lookup.engines.de(),
1547                lookup.context,
1548                src_type.type_id(),
1549                &src_type.span(),
1550            )
1551            .unwrap();
1552
1553            let dst_type = &intrinsic.type_arguments[1];
1554            let dst_ir_type = convert_resolved_type_id(
1555                lookup.engines.te(),
1556                lookup.engines.de(),
1557                lookup.context,
1558                dst_type.type_id(),
1559                &dst_type.span(),
1560            )
1561            .unwrap();
1562
1563            // check IR sizes match
1564            let src_ir_type_in_bytes = src_ir_type.size(lookup.context).in_bytes();
1565            let dst_ir_type_in_bytes = dst_ir_type.size(lookup.context).in_bytes();
1566            if src_ir_type_in_bytes != dst_ir_type_in_bytes {
1567                return Err(ConstEvalError::CompileError);
1568            }
1569
1570            fn append_bytes(
1571                ctx: &Context<'_>,
1572                bytes: &mut Vec<u8>,
1573                t: &Type,
1574                value: &ConstantValue,
1575            ) -> Result<(), ConstEvalError> {
1576                match t.get_content(ctx) {
1577                    TypeContent::Struct(fields) => match value {
1578                        ConstantValue::Struct(constants) => {
1579                            for (field_type, field) in fields.iter().zip(constants.iter()) {
1580                                append_bytes(ctx, bytes, field_type, &field.value)?;
1581                            }
1582                        }
1583                        _ => unreachable!(),
1584                    },
1585                    TypeContent::Array(item_type, size) => match value {
1586                        ConstantValue::Array(items) => {
1587                            assert!(*size as usize == items.len());
1588                            for item in items {
1589                                append_bytes(ctx, bytes, item_type, &item.value)?;
1590                            }
1591                        }
1592                        _ => unreachable!(),
1593                    },
1594                    TypeContent::Uint(8) => match value {
1595                        ConstantValue::Uint(v) => {
1596                            bytes.extend((*v as u8).to_be_bytes());
1597                        }
1598                        _ => unreachable!(),
1599                    },
1600                    TypeContent::Uint(16) => match value {
1601                        ConstantValue::Uint(v) => {
1602                            bytes.extend([0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
1603                            bytes.extend((*v as u16).to_be_bytes());
1604                        }
1605                        _ => unreachable!(),
1606                    },
1607                    TypeContent::Uint(32) => match value {
1608                        ConstantValue::Uint(v) => {
1609                            bytes.extend([0u8, 0u8, 0u8, 0u8]);
1610                            bytes.extend((*v as u32).to_be_bytes());
1611                        }
1612                        _ => unreachable!(),
1613                    },
1614                    TypeContent::Uint(64) => match value {
1615                        ConstantValue::Uint(v) => {
1616                            bytes.extend((*v).to_be_bytes());
1617                        }
1618                        _ => unreachable!(),
1619                    },
1620                    _ => return Err(ConstEvalError::CompileError),
1621                }
1622                Ok(())
1623            }
1624
1625            fn transmute_bytes(
1626                ctx: &Context<'_>,
1627                bytes: &mut std::io::Cursor<Vec<u8>>,
1628                t: &Type,
1629            ) -> Result<ConstantContent, ConstEvalError> {
1630                Ok(match t.get_content(ctx) {
1631                    TypeContent::Uint(8) => {
1632                        let mut buffer = [0u8];
1633                        let _ = bytes.read_exact(&mut buffer);
1634                        ConstantContent {
1635                            ty: Type::get_uint8(ctx),
1636                            value: ConstantValue::Uint(buffer[0] as u64),
1637                        }
1638                    }
1639                    TypeContent::Uint(16) => {
1640                        let mut buffer = [0u8; 8]; // u16 = u64 at runtime
1641                        let _ = bytes.read_exact(&mut buffer);
1642                        let buffer = [buffer[6], buffer[7]];
1643                        ConstantContent {
1644                            ty: Type::get_uint16(ctx),
1645                            value: ConstantValue::Uint(u16::from_be_bytes(buffer) as u64),
1646                        }
1647                    }
1648                    TypeContent::Uint(32) => {
1649                        let mut buffer = [0u8; 8]; // u32 = u64 at runtime
1650                        let _ = bytes.read_exact(&mut buffer);
1651                        let buffer = [buffer[4], buffer[5], buffer[6], buffer[7]];
1652                        ConstantContent {
1653                            ty: Type::get_uint32(ctx),
1654                            value: ConstantValue::Uint(u32::from_be_bytes(buffer) as u64),
1655                        }
1656                    }
1657                    TypeContent::Uint(64) => {
1658                        let mut buffer = [0u8; 8];
1659                        let _ = bytes.read_exact(&mut buffer);
1660                        ConstantContent {
1661                            ty: Type::get_uint64(ctx),
1662                            value: ConstantValue::Uint(u64::from_be_bytes(buffer)),
1663                        }
1664                    }
1665                    _ => return Err(ConstEvalError::CompileError),
1666                })
1667            }
1668
1669            let mut runtime_bytes = vec![];
1670            append_bytes(
1671                lookup.context,
1672                &mut runtime_bytes,
1673                &src_ir_type,
1674                &args[0].get_content(lookup.context).value,
1675            )?;
1676            let mut cursor = std::io::Cursor::new(runtime_bytes);
1677            let c = transmute_bytes(lookup.context, &mut cursor, &dst_ir_type)?;
1678            Ok(Some(Constant::unique(lookup.context, c)))
1679        }
1680        Intrinsic::Dbg => {
1681            unreachable!("__dbg should not exist in the typed tree")
1682        }
1683    }
1684}
1685
1686#[cfg(test)]
1687mod tests {
1688    use super::*;
1689    use sway_error::handler::Handler;
1690    use sway_features::ExperimentalFeatures;
1691    use sway_ir::Kind;
1692    use sway_types::ProgramId;
1693
1694    /// This function validates if an expression can be converted to [Constant].
1695    ///
1696    /// The flag `is_constant` is used to define if the expression should be convertible or not.
1697    /// `prefix` is any valid code at top level, useful to declare types.
1698    ///
1699    /// Example:
1700    ///
1701    /// ```rust,ignore
1702    /// assert_is_constant(true, "enum Color { Blue: u64 }", "Color::Blue(1)");
1703    /// assert_is_constant(false, "", "{return 1; 1}");
1704    /// ```
1705    ///
1706    /// It DOES NOT have access to the std lib, and constants, and other features that demand full compilation.
1707    fn assert_is_constant(is_constant: bool, prefix: &str, expr: &str) {
1708        let engines = Engines::default();
1709        let handler = Handler::default();
1710        let mut context = Context::new(engines.se(), ExperimentalFeatures::default());
1711        let mut md_mgr = MetadataManager::default();
1712        let core_lib = namespace::Package::new(
1713            sway_types::Ident::new_no_span("assert_is_constant_test".to_string()),
1714            None,
1715            ProgramId::new(0),
1716            false,
1717        );
1718
1719        let r = crate::compile_to_ast(
1720            &handler,
1721            &engines,
1722            format!("library; {prefix} fn f() -> u64 {{ {expr}; 0 }}")
1723                .as_str()
1724                .into(),
1725            core_lib,
1726            None,
1727            "test",
1728            None,
1729            ExperimentalFeatures::default(),
1730        );
1731
1732        let (errors, _warnings) = handler.consume();
1733
1734        if !errors.is_empty() {
1735            panic!("{:#?}", errors);
1736        }
1737
1738        let f = r.unwrap();
1739        let f = f.typed.unwrap();
1740
1741        let f = f
1742            .declarations
1743            .iter()
1744            .find_map(|x| match x {
1745                ty::TyDecl::FunctionDecl(x) => {
1746                    if engines.de().get_function(&x.decl_id).name.as_str() == "f" {
1747                        Some(x)
1748                    } else {
1749                        None
1750                    }
1751                }
1752                _ => None,
1753            })
1754            .expect("An function named `f` was not found.");
1755
1756        let f = engines.de().get_function(&f.decl_id);
1757        let expr_under_test = f.body.contents.first().unwrap();
1758
1759        let expr_under_test = match &expr_under_test.content {
1760            ty::TyAstNodeContent::Expression(expr_under_test) => expr_under_test.clone(),
1761            ty::TyAstNodeContent::Declaration(crate::language::ty::TyDecl::ConstantDecl(decl)) => {
1762                let decl = engines.de().get_constant(&decl.decl_id);
1763                decl.value.clone().unwrap()
1764            }
1765            x => todo!("{x:?}"),
1766        };
1767
1768        let module = Module::new(&mut context, Kind::Library);
1769        let actual_constant = compile_constant_expression_to_constant(
1770            &engines,
1771            &mut context,
1772            &mut md_mgr,
1773            module,
1774            None,
1775            None,
1776            &expr_under_test,
1777        );
1778
1779        match (is_constant, actual_constant) {
1780            (true, Ok(_)) => {}
1781            (true, Err(err)) => {
1782                panic!("Expression cannot be converted to constant: {expr:?}\nPrefix: {prefix:?}\nExpr:{expr_under_test:#?}\nError: {err:#?}");
1783            }
1784            (false, Ok(constant)) => {
1785                panic!("Expression unexpectedly can be converted to constant: {expr:?}\nPrefix: {prefix:?}\nExpr:{expr_under_test:#?}\nConstant: {constant:#?}");
1786            }
1787            (false, Err(_)) => {}
1788        }
1789    }
1790
1791    #[test]
1792    fn const_eval_test() {
1793        // Expressions that can be converted to constant
1794        assert_is_constant(true, "", "1");
1795        assert_is_constant(true, "", "true");
1796        assert_is_constant(true, "fn one() -> u64 { 1 }", "one()");
1797        assert_is_constant(true, "fn id(x: u64) -> u64 { x }", "id(1)");
1798        assert_is_constant(true, "enum Color { Blue: () }", "Color::Blue");
1799        assert_is_constant(true, "enum Color { Blue: u64 }", "Color::Blue(1)");
1800        assert_is_constant(true, "struct Person { age: u64 }", "Person { age: 1 }");
1801        assert_is_constant(true, "struct Person { age: u64 }", "Person { age: 1 }.age");
1802        assert_is_constant(
1803            true,
1804            "struct Person { age: u64 }",
1805            "Person { age: { let mut x = 0; x = 1; 1 } }",
1806        );
1807        assert_is_constant(true, "", "if true { 1 } else { 0 }");
1808        assert_is_constant(true, "", "(0,1).0");
1809        assert_is_constant(true, "", "[0,1][0]");
1810
1811        // u256
1812        assert_is_constant(
1813            true,
1814            "",
1815            "0x0000000000000000000000000000000000000000000000000000000000000001u256",
1816        );
1817        assert_is_constant(
1818            true,
1819            "",
1820            "__add(0x0000000000000000000000000000000000000000000000000000000000000001u256, 0x0000000000000000000000000000000000000000000000000000000000000001u256)",
1821        );
1822        assert_is_constant(
1823            true,
1824            "",
1825            "__eq(0x0000000000000000000000000000000000000000000000000000000000000001u256, 0x0000000000000000000000000000000000000000000000000000000000000001u256)",
1826        );
1827        assert_is_constant(
1828            true,
1829            "",
1830            "__gt(0x0000000000000000000000000000000000000000000000000000000000000001u256, 0x0000000000000000000000000000000000000000000000000000000000000001u256)",
1831        );
1832        assert_is_constant(
1833            true,
1834            "",
1835            "__lt(0x0000000000000000000000000000000000000000000000000000000000000001u256, 0x0000000000000000000000000000000000000000000000000000000000000001u256)",
1836        );
1837        assert_is_constant(
1838            true,
1839            "",
1840            "__lsh(0x0000000000000000000000000000000000000000000000000000000000000001u256, 2)",
1841        );
1842        assert_is_constant(
1843            true,
1844            "",
1845            "__not(0x0000000000000000000000000000000000000000000000000000000000000001u256)",
1846        );
1847
1848        // Expressions that cannot be converted to constant
1849        assert_is_constant(false, "", "{ return 1; }");
1850        assert_is_constant(false, "", "{ return 1; 1}");
1851        assert_is_constant(
1852            false,
1853            "enum Color { Blue: u64 }",
1854            "Color::Blue({ return 1; 1})",
1855        );
1856        assert_is_constant(
1857            false,
1858            "struct Person { age: u64 }",
1859            "Person { age: { return 1; 1} }",
1860        );
1861
1862        // At the moment this is not constant because of the "return"
1863        assert_is_constant(false, "fn id(x: u64) -> u64 { return x; }", "id(1)");
1864        assert_is_constant(false, "", "[0,1][2]");
1865        assert_is_constant(
1866            false,
1867            "enum Color { Blue: u64 }",
1868            "Color::Blue({return 1;})",
1869        );
1870
1871        // Code blocks that can be converted to constants
1872        assert_is_constant(true, "", "{ 1 }");
1873        assert_is_constant(true, "", "{ let a = 1; a }");
1874        assert_is_constant(true, "", "{ const a = 1; a }");
1875        assert_is_constant(true, "", "{ struct A {} 1 }");
1876        assert_is_constant(true, "fn id(x: u64) -> u64 { { let x = 2; }; x }", "id(1)");
1877
1878        // Code blocks that cannot be converted to constants
1879        assert_is_constant(false, "", "{ let a = 1; }");
1880        assert_is_constant(false, "", "{ const a = 1; }");
1881        assert_is_constant(false, "", "{ struct A {} }");
1882        assert_is_constant(false, "", "{ return 1; 1 }");
1883        assert_is_constant(false, "", "{ }");
1884        assert_is_constant(false, "fn id(x: u64) -> u64 { { return 1; }; x }", "id(1)");
1885    }
1886}