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 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 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 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 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}