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 pub top: Scope,
31 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 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 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 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 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 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 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}