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        if let Some(hint) = self.misspellings_hint_message() {
170            diag.hint(hint);
171        }
172        diag
173    }
174
175    pub fn misspellings_hint_message(&self) -> Option<EcoString> {
176        if self.possible_misspellings.is_empty() {
177            return None;
178        }
179
180        Some(eco_format!(
181            "Did you mean one of these?\n{}.",
182            self.possible_misspellings
183                .iter()
184                .map(|i| eco_format!("  - `{i}`"))
185                .collect::<Vec<_>>()
186                .join("\n"),
187        ))
188    }
189}
190
191impl<T> At<T> for Result<T, UnBoundError> {
192    fn at(self, span: Span) -> SourceResult<T> {
193        self.map_err(|err| eco_vec!(err.to_diag(span)))
194    }
195}
196
197#[derive(Debug, Default, Clone)]
198pub struct Scope {
199    map: IndexMap<EcoString, Binding>,
200}
201
202impl Trace for Scopes<'_> {
203    fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
204        self.top.visit_refs(f);
205        for scope in self.stack.iter() {
206            scope.visit_refs(f);
207        }
208    }
209}
210
211impl Trace for Scope {
212    fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
213        for v in self.map.values() {
214            v.value.visit_refs(f);
215        }
216    }
217}
218
219impl Scope {
220    pub fn try_bind(&mut self, name: EcoString, binding: Binding) -> StrResult<&mut Binding> {
221        Ok(match self.map.entry(name) {
222            Entry::Occupied(mut entry) => {
223                if entry.get().kind == BindingKind::Constant {
224                    bail!("Cannot redefine a constant in the same scope");
225                }
226                entry.insert(binding);
227                entry.into_mut()
228            }
229            Entry::Vacant(entry) => entry.insert(binding),
230        })
231    }
232
233    pub fn bind(&mut self, name: EcoString, binding: Binding) -> &mut Binding {
234        match self.map.entry(name) {
235            Entry::Occupied(mut entry) => {
236                entry.insert(binding);
237                entry.into_mut()
238            }
239            Entry::Vacant(entry) => entry.insert(binding),
240        }
241    }
242
243    pub fn define_func<T: NativeFunc>(&mut self) -> &mut Binding {
244        let data = T::data();
245        self.define(data.name, Func::from(data))
246    }
247
248    pub fn define_func_with_data(&mut self, data: &'static NativeFuncData) {
249        self.define(data.name, Func::from(data));
250    }
251
252    /// Define a variable as a constant value. It cannot be overwritten.
253    pub fn define(&mut self, name: &'static str, value: impl IntoValue) -> &mut Binding {
254        let binding = Binding::new(value, Span::detached())
255            .with_kind(BindingKind::Constant)
256            .with_visibility(Visibility::Public);
257        self.bind(name.into(), binding)
258    }
259
260    pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
261        let data = T::data();
262        self.define(data.name, Type::from(data))
263    }
264
265    pub fn bindings(&self) -> &IndexMap<EcoString, Binding> {
266        &self.map
267    }
268}
269
270impl Scope {
271    pub fn new() -> Self {
272        Default::default()
273    }
274
275    pub fn get(&self, name: &str) -> Option<&Binding> {
276        self.map.get(name)
277    }
278
279    pub fn get_mut(&mut self, name: &str) -> Option<&mut Binding> {
280        self.map.get_mut(name)
281    }
282
283    pub fn try_get(&self, name: &str) -> Result<&Binding, UnBoundError> {
284        self.map.get(name).ok_or_else(|| UnBoundError {
285            name: name.into(),
286            item: UnboundItem::Variable,
287            possible_misspellings: self.get_similar_bindings(name),
288        })
289    }
290
291    pub fn get_similar_bindings(&self, name: &str) -> Vec<EcoString> {
292        similar_idents(name, self.map.keys())
293    }
294}
295
296fn similar_idents(name: &str, idents: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<EcoString> {
297    let mut similar_idents: Vec<_> = idents
298        .into_iter()
299        .filter_map(|ident| {
300            let ident = ident.as_ref();
301            let score = jaro_winkler(name, ident);
302            (score > 0.8).then(|| (EcoString::from(ident), score))
303        })
304        .collect();
305    similar_idents.sort_unstable_by(|a, b| b.1.total_cmp(&a.1));
306
307    let mut seen = HashSet::new();
308
309    similar_idents
310        .into_iter()
311        .filter_map(|(ident, _)| {
312            if seen.insert(ident.clone()) {
313                Some(ident)
314            } else {
315                None
316            }
317        })
318        .collect()
319}
320
321#[derive(Debug, Clone)]
322pub struct Binding {
323    value: Value,
324    span: Span,
325    kind: BindingKind,
326    pub visibility: Visibility,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
330pub enum BindingKind {
331    Immutable { first_assign: Option<Span> },
332    Mutable,
333    Uninitialized,
334    UninitializedMutable,
335    Constant,
336    Param,
337    ParamMut,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
341pub enum Visibility {
342    Public,
343    Private,
344}
345
346impl BindingKind {
347    pub fn is_mut(&self) -> bool {
348        matches!(
349            self,
350            BindingKind::Mutable | BindingKind::ParamMut | BindingKind::UninitializedMutable
351        )
352    }
353}
354
355impl Binding {
356    pub fn new(value: impl IntoValue, span: Span) -> Self {
357        Self {
358            kind: BindingKind::Immutable { first_assign: None },
359            value: value.into_value(),
360            visibility: Visibility::Private,
361            span,
362        }
363    }
364
365    pub fn with_kind(self, kind: BindingKind) -> Self {
366        Self { kind, ..self }
367    }
368
369    pub fn with_visibility(self, visibility: Visibility) -> Self {
370        Self { visibility, ..self }
371    }
372
373    pub fn detached(value: impl IntoValue) -> Self {
374        Self::new(value, Span::detached())
375    }
376
377    pub fn read(&self) -> &Value {
378        &self.value
379    }
380
381    /// Read the value behind the binding.
382    ///
383    /// A warning is emitted to the sink if the variable was not yet initialized.
384    pub fn read_checked(&self, access_span: Span, sink: &mut Sink) -> &Value {
385        if self.is_uninitialized() {
386            sink.warn(
387                warning!(access_span, "use of variable before it has been initialized";)
388                    .with_code(&W0001_USED_UNINITIALIZED_VARIABLE)
389                    .with_label_message("use of uninitialized variable")
390                    .with_label(Label::secondary(
391                        self.span,
392                        "variable declared here without an initial value",
393                    ))
394                    .with_note("uninitialised variables evaluate to `()` by default"),
395            )
396        }
397        self.read()
398    }
399
400    /// Get a mutable reference to the value behind the binding
401    ///
402    /// Returns an error if the value is not mutable.
403    ///
404    /// If the binding was not yet initialized, its kind will be updated to the corresponding
405    /// initialized kind.
406    pub fn write(&mut self, access_span: Span) -> SourceResult<&mut Value> {
407        match self.kind {
408            BindingKind::ParamMut | BindingKind::Mutable => Ok(&mut self.value),
409            BindingKind::Immutable { first_assign } => Err(eco_vec![
410                error!(
411                    access_span,
412                    "cannot reassign to a variable declared as immutable"
413                )
414                .with_code(&E0004_MUTATE_IMMUTABLE_VARIABLE)
415                .with_label(Label::secondary(self.span, "was defined as immutable here"))
416                .pipe(|diag| {
417                    if let Some(first_assign) = first_assign {
418                        diag.with_label(Label::secondary(
419                            first_assign,
420                            "first assignment occurred here",
421                        ))
422                        .with_label_message("cannot reassign an immutable variable")
423                    } else {
424                        diag.with_label_message("is immutable")
425                    }
426                })
427                .with_note("variables are immutable by default")
428                .with_hint("make the variable mutable by writing `let mut`")
429            ]),
430            BindingKind::Param => Err(eco_vec![
431                error!(access_span, "cannot assign to an immutable parameter")
432                    .with_label_message("is an immutable parameter")
433                    .with_label(Label::secondary(
434                        self.span,
435                        "this parameter is immutable, add `mut` to make it mutable"
436                    ))
437            ]),
438            BindingKind::Constant => Err(eco_vec![
439                error!(access_span, "cannot assign to a constant variable")
440                    .with_label_message("is constant")
441                    .with_label(Label::secondary(self.span, "was defined as constant here"))
442            ]),
443            BindingKind::Uninitialized => {
444                self.kind = BindingKind::Immutable {
445                    first_assign: Some(access_span),
446                };
447                Ok(&mut self.value)
448            }
449            BindingKind::UninitializedMutable => {
450                self.kind = BindingKind::Mutable;
451                Ok(&mut self.value)
452            }
453        }
454    }
455
456    pub fn is_uninitialized(&self) -> bool {
457        matches!(
458            self.kind,
459            BindingKind::Uninitialized | BindingKind::UninitializedMutable
460        )
461    }
462
463    pub fn span(&self) -> Span {
464        self.span
465    }
466
467    pub fn kind(&self) -> BindingKind {
468        self.kind
469    }
470
471    pub fn is_mutable(&self) -> bool {
472        self.kind.is_mut()
473    }
474}