compose_library/foundations/
module.rs1use 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 name: EcoString,
15 inner: Arc<Inner>,
16}
17
18#[derive(Debug, Clone)]
19pub struct Inner {
20 scope: Scope,
22 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
32impl 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
83impl 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
111impl 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}