compose_library/foundations/
module.rs

1use crate::{Scope, SyntaxContext, Trace};
2use compose_library::diag::{bail, error, SourceResult};
3use compose_library::{Sink, UntypedRef, Value};
4use compose_macros::ty;
5use compose_syntax::ast::{AstNode, LetBinding};
6use compose_syntax::{FileId, Fix, FixBuilder, Label, Span};
7use ecow::EcoString;
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
11#[ty(cast)]
12pub struct Module {
13    /// The name of the module.
14    name: EcoString,
15    inner: Arc<Inner>,
16}
17
18#[derive(Debug, Clone)]
19pub struct Inner {
20    /// The scope containing the module's definitions.
21    scope: Scope,
22    /// The file that defines this module. Or `None` if the module is defined in memory.
23    file_id: Option<FileId>,
24}
25
26impl PartialEq for Module {
27    fn eq(&self, other: &Self) -> bool {
28        self.name == other.name && self.inner.file_id == other.inner.file_id
29    }
30}
31
32/// path access
33impl Module {
34    pub fn path(
35        &self,
36        path: &str,
37        access_span: Span,
38        sink: &mut Sink,
39        ctx: &SyntaxContext,
40    ) -> SourceResult<&Value> {
41        let Some(binding) = self.inner.scope.get(path) else {
42            bail!(
43                access_span,
44                "module `{}` has no binding named `{path}`",
45                self.name
46            );
47        };
48
49        if binding.visibility != crate::Visibility::Public {
50            let mut err = error!(
51                access_span, "cannot access private binding `{path}` in module `{}`", self.name;
52                label_message: "this binding is private";
53                label: Label::secondary(binding.span(), "declared as private here");
54                note: "bindings are private to their module unless explicitly marked `pub`"
55            );
56
57            if let Some(fix) = add_pub_fix(binding.span(), ctx) {
58                err.add_fix(fix);
59            }
60
61            bail!(err);
62        }
63
64        Ok(binding.read_checked(access_span, sink))
65    }
66}
67
68pub fn add_pub_fix(binding_span: Span, ctx: &SyntaxContext) -> Option<Fix> {
69    let source = ctx.world.related_source(binding_span)?;
70    let node = source.find(binding_span)?;
71
72    let let_binding = node.closest_parent_as::<LetBinding>()?;
73    let mut fix_builder = FixBuilder::new(
74        "consider marking this binding as public",
75        let_binding.span(),
76    );
77
78    fix_builder.insert_before(&let_binding, "pub ");
79
80    Some(fix_builder.build())
81}
82
83/// Creators
84impl Module {
85    pub fn new(name: impl Into<EcoString>, scope: Scope) -> Self {
86        Self {
87            name: name.into(),
88            inner: Arc::new(Inner {
89                scope,
90                file_id: None,
91            }),
92        }
93    }
94
95    pub fn with_file_id(mut self, file_id: FileId) -> Self {
96        Arc::make_mut(&mut self.inner).file_id = Some(file_id);
97        self
98    }
99
100    pub fn with_name(mut self, name: impl Into<EcoString>) -> Self {
101        self.name = name.into();
102        self
103    }
104
105    pub fn with_scope(mut self, scope: Scope) -> Self {
106        Arc::make_mut(&mut self.inner).scope = scope;
107        self
108    }
109}
110
111/// Accessors
112impl Module {
113    pub fn name(&self) -> &EcoString {
114        &self.name
115    }
116
117    pub fn scope(&self) -> &Scope {
118        &self.inner.scope
119    }
120
121    pub fn scope_mut(&mut self) -> &mut Scope {
122        &mut Arc::make_mut(&mut self.inner).scope
123    }
124
125    pub fn file_id(&self) -> Option<FileId> {
126        self.inner.file_id
127    }
128}
129
130impl Trace for Module {
131    fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
132        self.inner.scope.visit_refs(f);
133    }
134}