compose_library/foundations/
scope.rs

1use crate::diag::{At, IntoSourceDiagnostic, SourceDiagnostic, SourceResult, error, warning};
2use crate::{IntoValue, Trace};
3use crate::{Library, NativeFuncData, NativeType, Sink, Type, Value};
4use compose_error_codes::{
5    E0004_MUTATE_IMMUTABLE_VARIABLE, E0011_UNBOUND_VARIABLE, W0001_USED_UNINITIALIZED_VARIABLE,
6};
7use compose_library::diag::{StrResult, bail};
8use compose_library::{Func, NativeFunc, UntypedRef};
9use compose_syntax::{Label, Span};
10use ecow::{EcoString, eco_format, eco_vec};
11use indexmap::IndexMap;
12use indexmap::map::Entry;
13use std::collections::HashSet;
14use std::fmt::Debug;
15use std::hash::Hash;
16use std::iter;
17use std::sync::LazyLock;
18use strsim::jaro_winkler;
19use tap::Pipe;
20
21pub trait NativeScope {
22    fn scope() -> &'static Scope;
23}
24
25pub static EMPTY_SCOPE: LazyLock<Scope> = LazyLock::new(Scope::new);
26
27#[derive(Default, Clone)]
28pub struct Scopes<'a> {
29    /// The current scope.
30    pub top: Scope,
31    /// The rest of the scopes.
32    pub stack: Vec<Scope>,
33    pub lib: Option<&'a Library>,
34}
35
36impl Debug for Scopes<'_> {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("Scopes")
39            .field("top", &self.top)
40            .field("stack", &self.stack)
41            .finish()
42    }
43}
44
45impl<'a> Scopes<'a> {
46    pub fn new(lib: Option<&'a Library>) -> Self {
47        Self {
48            top: Scope::new(),
49            stack: Vec::new(),
50            lib,
51        }
52    }
53
54    pub fn enter(&mut self) {
55        self.stack.push(std::mem::take(&mut self.top));
56    }
57
58    pub fn exit(&mut self) {
59        self.top = self.stack.pop().expect("Scope stack underflow");
60    }
61
62    pub fn get(&self, name: &str) -> Result<&Binding, VariableAccessError> {
63        iter::once(&self.top)
64            .chain(self.stack.iter().rev())
65            .chain(self.lib.iter().map(|lib| lib.global.scope()))
66            .find_map(|scope| scope.get(name))
67            .ok_or_else(|| self.unbound_error(name.into()))
68    }
69
70    pub fn get_mut(&mut self, name: &str) -> Result<&mut Binding, VariableAccessError> {
71        // Check if the variable is defined in the current scope first.
72        // This is done separately because creating an error requires access to `&self`,
73        // but we need a mutable borrow later.
74        // So we call `get(name)?` here to handle errors, then search mutably afterward.
75        let _ = self.get(name)?;
76
77        iter::once(&mut self.top)
78            .chain(self.stack.iter_mut().rev())
79            .find_map(|scope| scope.get_mut(name))
80            .ok_or_else(
81                || match self.lib.and_then(|base| base.global.scope().get(name)) {
82                    Some(_) => VariableAccessError::MutateConstant(name.into()),
83                    None => {
84                        debug_assert!(false, "This should have been caught by the get() call");
85                        VariableAccessError::Unbound(UnBoundError {
86                            name: name.into(),
87                            item: UnboundItem::Variable,
88                            possible_misspellings: Vec::new(),
89                        })
90                    }
91                },
92            )
93    }
94
95    fn unbound_error(&self, name: EcoString) -> VariableAccessError {
96        let all_idents = iter::once(&self.top)
97            .chain(&self.stack)
98            .chain(self.lib.iter().map(|lib| lib.global.scope()))
99            .flat_map(|scope| scope.map.keys());
100
101        VariableAccessError::Unbound(UnBoundError {
102            possible_misspellings: similar_idents(&name, all_idents),
103            item: UnboundItem::Variable,
104            name,
105        })
106    }
107}
108
109#[derive(Debug, Clone)]
110pub enum VariableAccessError {
111    Unbound(UnBoundError),
112    /// Attempted to mutate a constant. Contains the name of the constant that was attempted to mutate.
113    MutateConstant(EcoString),
114}
115
116impl IntoSourceDiagnostic for VariableAccessError {
117    fn into_source_diagnostic(self, span: Span) -> SourceDiagnostic {
118        match self {
119            VariableAccessError::Unbound(err) => err.to_diag(span),
120            VariableAccessError::MutateConstant(name) => error!(
121                span, "Cannot mutate constant `{name}`";
122                label_message: "is a constant from the standard library";
123                hint: "Constants can be shadowed, but not mutated.";
124            ),
125        }
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct UnBoundError {
131    pub name: EcoString,
132    pub item: UnboundItem,
133    pub possible_misspellings: Vec<EcoString>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub enum UnboundItem {
138    Variable,
139    /// A field or method (optionally with the name of the type)
140    FieldOrMethod(Option<EcoString>),
141    AssociatedFieldOrFunction(EcoString),
142}
143
144impl UnBoundError {
145    pub fn with_item(self, item: UnboundItem) -> Self {
146        Self { item, ..self }
147    }
148    pub fn to_diag(self, span: Span) -> SourceDiagnostic {
149        let mut diag = match &self.item {
150            UnboundItem::Variable => error!(
151                span, "Unbound variable: `{}`", self.name;
152                label_message: "this variable is unbound here";
153                code: &E0011_UNBOUND_VARIABLE
154            ),
155            UnboundItem::FieldOrMethod(Some(ty)) => error!(
156                span, "type `{ty}` has no field or method named `{}`", self.name;
157                label_message: "this field or method does not exist on type `{ty}`";
158            ),
159            UnboundItem::FieldOrMethod(_) => error!(
160                span, "Unbound field or method: `{}`", self.name;
161                label_message: "this field or method is unbound here";
162            ),
163            UnboundItem::AssociatedFieldOrFunction(assoc) => error!(
164                span, "no associated function or field named `{}` found for type `{}`", self.name, assoc;
165                label_message: "this field or method is unbound here";
166            ),
167        };
168
169        self.apply_hint(&mut diag);
170        diag
171    }
172
173    pub fn apply_hint(self, diag: &mut SourceDiagnostic) {
174        if !self.possible_misspellings.is_empty() {
175            diag.hint(eco_format!(
176                "Did you mean one of these?\n{}.",
177                self.possible_misspellings
178                    .iter()
179                    .map(|i| eco_format!("  - `{i}`"))
180                    .collect::<Vec<_>>()
181                    .join("\n"),
182            ))
183        }
184    }
185}
186
187impl<T> At<T> for Result<T, UnBoundError> {
188    fn at(self, span: Span) -> SourceResult<T> {
189        self.map_err(|err| eco_vec!(err.to_diag(span)))
190    }
191}
192
193#[derive(Debug, Default, Clone)]
194pub struct Scope {
195    map: IndexMap<EcoString, Binding>,
196}
197
198impl Trace for Scopes<'_> {
199    fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
200        self.top.visit_refs(f);
201        for scope in self.stack.iter() {
202            scope.visit_refs(f);
203        }
204    }
205}
206
207impl Trace for Scope {
208    fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
209        for v in self.map.values() {
210            v.value.visit_refs(f);
211        }
212    }
213}
214
215impl Scope {
216    pub fn try_bind(&mut self, name: EcoString, binding: Binding) -> StrResult<&mut Binding> {
217        Ok(match self.map.entry(name) {
218            Entry::Occupied(mut entry) => {
219                if entry.get().kind == BindingKind::Constant {
220                    bail!("Cannot redefine a constant in the same scope");
221                }
222                entry.insert(binding);
223                entry.into_mut()
224            }
225            Entry::Vacant(entry) => entry.insert(binding),
226        })
227    }
228
229    pub fn bind(&mut self, name: EcoString, binding: Binding) -> &mut Binding {
230        match self.map.entry(name) {
231            Entry::Occupied(mut entry) => {
232                entry.insert(binding);
233                entry.into_mut()
234            }
235            Entry::Vacant(entry) => entry.insert(binding),
236        }
237    }
238
239    pub fn define_func<T: NativeFunc>(&mut self) -> &mut Binding {
240        let data = T::data();
241        self.define(data.name, Func::from(data))
242    }
243
244    pub fn define_func_with_data(&mut self, data: &'static NativeFuncData) {
245        self.define(data.name, Func::from(data));
246    }
247
248    /// Define a variable as a constant value. It cannot be overwritten.
249    pub fn define(&mut self, name: &'static str, value: impl IntoValue) -> &mut Binding {
250        let binding = Binding::new(value, Span::detached())
251            .with_kind(BindingKind::Constant)
252            .with_visibility(Visibility::Public);
253        self.bind(name.into(), binding)
254    }
255
256    pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
257        let data = T::data();
258        self.define(data.name, Type::from(data))
259    }
260
261    pub fn bindings(&self) -> &IndexMap<EcoString, Binding> {
262        &self.map
263    }
264}
265
266impl Scope {
267    pub fn new() -> Self {
268        Default::default()
269    }
270
271    pub fn get(&self, name: &str) -> Option<&Binding> {
272        self.map.get(name)
273    }
274
275    pub fn get_mut(&mut self, name: &str) -> Option<&mut Binding> {
276        self.map.get_mut(name)
277    }
278
279    pub fn try_get(&self, name: &str) -> Result<&Binding, UnBoundError> {
280        self.map.get(name).ok_or_else(|| UnBoundError {
281            name: name.into(),
282            item: UnboundItem::Variable,
283            possible_misspellings: self.get_similar_bindings(name),
284        })
285    }
286
287    pub fn get_similar_bindings(&self, name: &str) -> Vec<EcoString> {
288        similar_idents(name, self.map.keys())
289    }
290}
291
292fn similar_idents(name: &str, idents: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<EcoString> {
293    let mut similar_idents: Vec<_> = idents
294        .into_iter()
295        .filter_map(|ident| {
296            let ident = ident.as_ref();
297            let score = jaro_winkler(name, ident);
298            (score > 0.8).then(|| (EcoString::from(ident), score))
299        })
300        .collect();
301    similar_idents.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
302
303    let mut seen = HashSet::new();
304
305    similar_idents
306        .into_iter()
307        .filter_map(|(ident, _)| {
308            if seen.insert(ident.clone()) {
309                Some(ident)
310            } else {
311                None
312            }
313        })
314        .collect()
315}
316
317#[derive(Debug, Clone)]
318pub struct Binding {
319    value: Value,
320    span: Span,
321    kind: BindingKind,
322    pub visibility: Visibility,
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
326pub enum BindingKind {
327    Immutable { first_assign: Option<Span> },
328    Mutable,
329    Uninitialized,
330    UninitializedMutable,
331    Constant,
332    Param,
333    ParamMut,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
337pub enum Visibility {
338    Public,
339    Private,
340}
341
342impl BindingKind {
343    pub fn is_mut(&self) -> bool {
344        matches!(
345            self,
346            BindingKind::Mutable | BindingKind::ParamMut | BindingKind::UninitializedMutable
347        )
348    }
349}
350
351impl Binding {
352    pub fn new(value: impl IntoValue, span: Span) -> Self {
353        Self {
354            kind: BindingKind::Immutable { first_assign: None },
355            value: value.into_value(),
356            visibility: Visibility::Private,
357            span,
358        }
359    }
360
361    pub fn with_kind(self, kind: BindingKind) -> Self {
362        Self { kind, ..self }
363    }
364
365    pub fn with_visibility(self, visibility: Visibility) -> Self {
366        Self { visibility, ..self }
367    }
368
369    pub fn detached(value: impl IntoValue) -> Self {
370        Self::new(value, Span::detached())
371    }
372
373    pub fn read(&self) -> &Value {
374        &self.value
375    }
376
377    /// Read the value behind the binding.
378    ///
379    /// A warning is emitted to the sink if the variable was not yet initialized.
380    pub fn read_checked(&self, access_span: Span, sink: &mut Sink) -> &Value {
381        if self.is_uninitialized() {
382            sink.warn(
383                warning!(access_span, "use of variable before it has been initialized";)
384                    .with_code(&W0001_USED_UNINITIALIZED_VARIABLE)
385                    .with_label_message("use of uninitialized variable")
386                    .with_label(Label::secondary(
387                        self.span,
388                        "variable declared here without an initial value",
389                    ))
390                    .with_note("uninitialised variables evaluate to `()` by default"),
391            )
392        }
393        self.read()
394    }
395
396    /// Get a mutable reference to the value behind the binding
397    ///
398    /// Returns an error if the value is not mutable.
399    ///
400    /// If the binding was not yet initialized, its kind will be updated to the corresponding
401    /// initialized kind.
402    pub fn write(&mut self, access_span: Span) -> SourceResult<&mut Value> {
403        match self.kind {
404            BindingKind::ParamMut | BindingKind::Mutable => Ok(&mut self.value),
405            BindingKind::Immutable { first_assign } => Err(eco_vec![
406                error!(
407                    access_span,
408                    "cannot reassign to a variable declared as immutable"
409                )
410                .with_code(&E0004_MUTATE_IMMUTABLE_VARIABLE)
411                .with_label(Label::secondary(self.span, "was defined as immutable here"))
412                .pipe(|diag| {
413                    if let Some(first_assign) = first_assign {
414                        diag.with_label(Label::secondary(
415                            first_assign,
416                            "first assignment occurred here",
417                        ))
418                        .with_label_message("cannot reassign an immutable variable")
419                    } else {
420                        diag.with_label_message("is immutable")
421                    }
422                })
423                .with_note("variables are immutable by default")
424                .with_hint("make the variable mutable by writing `let mut`")
425            ]),
426            BindingKind::Param => Err(eco_vec![
427                error!(access_span, "cannot assign to an immutable parameter")
428                    .with_label_message("is an immutable parameter")
429                    .with_label(Label::secondary(
430                        self.span,
431                        "this parameter is immutable, add `mut` to make it mutable"
432                    ))
433            ]),
434            BindingKind::Constant => Err(eco_vec![
435                error!(access_span, "cannot assign to a constant variable")
436                    .with_label_message("is constant")
437                    .with_label(Label::secondary(self.span, "was defined as constant here"))
438            ]),
439            BindingKind::Uninitialized => {
440                self.kind = BindingKind::Immutable {
441                    first_assign: Some(access_span),
442                };
443                Ok(&mut self.value)
444            }
445            BindingKind::UninitializedMutable => {
446                self.kind = BindingKind::Mutable;
447                Ok(&mut self.value)
448            }
449        }
450    }
451
452    pub fn is_uninitialized(&self) -> bool {
453        matches!(
454            self.kind,
455            BindingKind::Uninitialized | BindingKind::UninitializedMutable
456        )
457    }
458
459    pub fn span(&self) -> Span {
460        self.span
461    }
462
463    pub fn kind(&self) -> BindingKind {
464        self.kind
465    }
466
467    pub fn is_mutable(&self) -> bool {
468        self.kind.is_mut()
469    }
470}