Skip to main content

microcad_lang/eval/statements/
assignment_statement.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use microcad_lang_base::PushDiag;
5use microcad_lang_base::SrcReferrer;
6
7use crate::{eval::*, symbol::SymbolDef, value::*};
8
9impl Assignment {
10    /// Check if the specified type matches the found type.
11    pub fn type_check(&self, found: Type) -> EvalResult<()> {
12        if let Some(ty) = &self.specified_type {
13            if ty.ty() != found {
14                return Err(EvalError::TypeMismatch {
15                    id: self.id(),
16                    expected: ty.ty(),
17                    found,
18                });
19            }
20        }
21
22        Ok(())
23    }
24}
25
26impl Eval<()> for AssignmentStatement {
27    fn eval(&self, context: &mut EvalContext) -> EvalResult<()> {
28        log::debug!("Evaluating assignment statement:\n{self}");
29
30        let assignment = &self.assignment;
31
32        // evaluate assignment expression
33        let new_value: Value = assignment.expression.eval(context)?;
34        if let Err(err) = assignment.type_check(new_value.ty()) {
35            context.error(self, err)?;
36            return Ok(());
37        }
38
39        // apply any attributes to model value
40        let new_value = match new_value {
41            Value::Model(model) => {
42                model.set_id(assignment.id());
43                let attributes = self.attribute_list.eval(context)?;
44                model.borrow_mut().attributes = attributes.clone();
45                Value::Model(model)
46            }
47            value => {
48                // all other values can't have attributes
49                if !self.attribute_list.is_empty() {
50                    context.error(
51                        &self.attribute_list,
52                        AttributeError::CannotAssignAttribute(
53                            self.assignment.expression.to_string(),
54                        ),
55                    )?;
56                }
57                value
58            }
59        };
60
61        let mut abort = false;
62
63        // lookup if we find any existing symbol
64        if let Ok(symbol) = context.lookup(
65            &QualifiedName::from_id(assignment.id()),
66            LookupTarget::Value,
67        ) {
68            let err = symbol.with_def_mut(|def| match def {
69                SymbolDef::Value(id, value) => {
70                    if value.is_invalid() {
71                        *value = new_value.clone();
72                        None
73                    } else {
74                        Some((
75                            assignment.id(),
76                            EvalError::ValueAlreadyDefined {
77                                location: assignment.src_ref(),
78                                name: id.clone(),
79                                value: value.to_string(),
80                                previous_location: id.src_ref(),
81                            },
82                        ))
83                    }
84                }
85                SymbolDef::Assignment(..) => {
86                    abort = true;
87                    None
88                }
89                _ => Some((assignment.id(), EvalError::NotAnLValue(assignment.id()))),
90            });
91            // because logging is blocked while `symbol.borrow_mut()` it must run outside the borrow
92            if let Some((id, err)) = err {
93                context.error(&id, err)?;
94            }
95        }
96
97        if !abort {
98            // now check what to do with the value
99            match assignment.qualifier() {
100                Qualifier::Const => {
101                    if context.get_property(assignment.id_ref()).is_ok() {
102                        todo!("property with that name exists")
103                    }
104
105                    let symbol = context.lookup(&assignment.id().into(), LookupTarget::Value);
106                    match symbol {
107                        Ok(symbol) => {
108                            if let Err(err) = symbol.set_value(new_value) {
109                                context.error(self, err)?
110                            }
111                        }
112                        Err(err) => context.error(self, err)?,
113                    }
114                }
115                Qualifier::Value => {
116                    let result = if context.get_property(assignment.id_ref()).is_ok() {
117                        if context.is_init() {
118                            context.init_property(assignment.id(), new_value)
119                        } else {
120                            todo!("property with that name exists")
121                        }
122                    } else {
123                        context.set_local_value(assignment.id(), new_value)
124                    };
125                    if let Err(err) = result {
126                        context.error(self, err)?;
127                    }
128                }
129                Qualifier::Prop => {
130                    if context.get_local_value(assignment.id_ref()).is_ok() {
131                        todo!("local value with that name exists")
132                    }
133                    if let Err(err) = context.init_property(assignment.id(), new_value) {
134                        context.error(self, err)?;
135                    }
136                }
137            }
138        }
139
140        Ok(())
141    }
142}
143
144impl Eval<Value> for Assignment {
145    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
146        self.expression.eval(context)
147    }
148}