compose_library/foundations/
func.rs1use crate::diag::{SourceResult, StrResult, bail};
2use crate::foundations::args::Args;
3use crate::vm::Vm;
4use crate::{Sink, Trace, Value};
5use compose_error_codes::E0010_UNCAPTURED_VARIABLE;
6use compose_library::diag::{Spanned, error};
7use compose_library::{Scope, UntypedRef};
8use compose_macros::{cast, ty};
9use compose_syntax::ast::{AstNode};
10use compose_syntax::{Label, Span, SyntaxNode, ast};
11use compose_utils::Static;
12use ecow::{EcoString, eco_format, eco_vec};
13use std::collections::HashMap;
14use std::fmt;
15use std::sync::LazyLock;
16use tap::Tap;
17
18#[derive(Clone, Debug, PartialEq)]
19#[ty(cast)]
20pub struct Func {
21 pub kind: FuncKind,
22 pub span: Span,
23}
24
25impl Func {
26 pub(crate) fn spanned(mut self, span: Span) -> Func {
27 if self.span.is_detached() {
28 self.span = span;
29 }
30 self
31 }
32
33 pub(crate) fn named(mut self, name: Spanned<EcoString>) -> Func {
34 if let FuncKind::Closure(closure) = &mut self.kind {
35 closure.unresolved_captures.remove(&name.value);
36 closure.name = Some(name);
37 }
38 self
39 }
40
41 pub(crate) fn resolve(&self) -> SourceResult<()> {
42 match &self.kind {
43 FuncKind::Closure(closure) => {
44 closure.resolve_captures()?;
45 }
46 FuncKind::Native(_) => {}
47 }
48 Ok(())
49 }
50
51 pub(crate) fn span(&self) -> Span {
52 self.span
53 }
54}
55
56impl fmt::Display for Func {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match &self.kind {
59 FuncKind::Native(native) => write!(f, "{}", native.0.name),
60 FuncKind::Closure(closure) => closure.fmt(f),
61 }
62 }
63}
64
65impl Trace for Func {
66 fn visit_refs(&self, f: &mut dyn FnMut(UntypedRef)) {
67 match &self.kind {
68 FuncKind::Native(native) => {
69 native.scope.visit_refs(f);
70 }
71 FuncKind::Closure(closure) => {
72 closure.captured.visit_refs(f);
73 closure.defaults.iter().for_each(|v| v.visit_refs(f));
74 }
75 }
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
80pub enum FuncKind {
81 Native(Static<NativeFuncData>),
82 Closure(Box<Closure>),
83}
84
85impl Func {
86 pub fn call(&self, vm: &mut dyn Vm, args: Args) -> SourceResult<Value> {
87 vm.call_func(self, args)
88 }
89
90 pub fn scope(&self) -> Option<&'static Scope> {
91 match &self.kind {
92 FuncKind::Native(native) => Some(&native.0.scope),
93 FuncKind::Closure(_) => None,
94 }
95 }
96
97 pub fn name(&self) -> Option<&str> {
98 match &self.kind {
99 FuncKind::Native(native) => Some(native.0.name),
100 FuncKind::Closure(_) => None,
101 }
102 }
103
104 pub fn field(&self, field: &str, access_span: Span, sink: &mut Sink) -> StrResult<&Value> {
105 let scope = self
106 .scope()
107 .ok_or("Cannot access fields on user-defined functions")?;
108 match scope.get(field) {
109 Some(binding) => Ok(binding.read_checked(access_span, sink)),
110 None => match self.name() {
111 Some(name) => bail!("function `{name}` does not contain field `{field}`"),
112 None => bail!("Function does not contain field `{field}`"),
113 },
114 }
115 }
116
117 pub fn path(&self, path: &str, access_span: Span, sink: &mut Sink) -> StrResult<&Value> {
118 let scope = self
119 .scope()
120 .ok_or("Cannot access fields on user-defined functions")?;
121 match scope.get(path) {
122 Some(binding) => Ok(binding.read_checked(access_span, sink)),
123 None => match self.name() {
124 Some(name) => bail!("function `{name}` does not contain associated field `{path}`"),
125 None => bail!("Function does not contain associated field `{path}`"),
126 },
127 }
128 }
129
130 pub fn is_associated_function(&self) -> bool {
131 match self.kind {
132 FuncKind::Native(n) => match n.fn_type {
133 FuncType::Method => false,
134 FuncType::MethodMut => false,
135 FuncType::Associated => true,
136 },
137 FuncKind::Closure(_) => false,
138 }
139 }
140
141 pub fn requires_mut_self(&self) -> bool {
142 match self.kind {
143 FuncKind::Native(n) => match n.fn_type {
144 FuncType::Method => false,
145 FuncType::MethodMut => true,
146 FuncType::Associated => false,
147 },
148 FuncKind::Closure(_) => false,
149 }
150 }
151}
152
153pub trait NativeFunc {
154 fn data() -> &'static NativeFuncData;
155}
156
157#[derive(Debug)]
158pub struct NativeFuncData {
159 pub closure: fn(&mut dyn Vm, &mut Args) -> SourceResult<Value>,
160 pub name: &'static str,
161 pub scope: LazyLock<&'static Scope>,
162 pub fn_type: FuncType,
163}
164
165impl NativeFuncData {
166 pub fn call(&self, vm: &mut dyn Vm, mut args: Args) -> SourceResult<Value> {
167 (self.closure)(vm, &mut args)
168 }
169}
170
171#[derive(Debug, Clone)]
172pub struct Closure {
173 pub node: SyntaxNode,
174 pub defaults: Vec<Value>,
175 pub num_pos_params: usize,
176 pub name: Option<Spanned<EcoString>>,
177 pub captured: Scope,
178 pub unresolved_captures: HashMap<EcoString, Span>,
179}
180
181impl PartialEq for Closure {
182 fn eq(&self, other: &Self) -> bool {
183 self.node == other.node
184 }
185}
186
187impl Closure {
188 pub fn resolve_captures(&self) -> SourceResult<()> {
189 if !self.unresolved_captures.is_empty() {
190 let mut captures = self.unresolved_captures.iter();
191
192 let names = self
193 .unresolved_captures
194 .keys()
195 .map(|name| name.trim())
196 .collect::<Vec<_>>()
197 .tap_mut(|v| v.sort_unstable())
198 .join(", ");
199
200 let params = self
201 .node
202 .cast::<ast::Lambda>()
203 .expect("Closure contains non lambda node")
204 .params()
205 .children()
206 .map(|p| p.to_untyped().to_text())
207 .collect::<Vec<_>>()
208 .join(", ");
209
210 let (first_name, first_span) = captures.next().expect("unresolved captures was checked to be non-empty");
211
212 let mut err = error!(*first_span, "closure uses outer variables that are not captured";
213 label_message: "outer variable `{first_name}` used here";
214 hint: "explicitly capture them by adding them to a capture list: `{{ |{names}| {params}=> ... }}`";
215 code: &E0010_UNCAPTURED_VARIABLE
216 );
217
218 for (name, span) in captures {
219 err = err.with_label(Label::primary(
220 *span,
221 eco_format!("outer variable `{name}` used here"),
222 ));
223 }
224
225 return Err(eco_vec!(err));
226 }
227 Ok(())
228 }
229}
230
231impl fmt::Display for Closure {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 let closure: ast::Lambda = self.node.cast().expect("closure");
234 let params = closure.params().to_untyped().to_text();
235
236 write!(f, "{} => ...", params)
237 }
238}
239
240#[derive(Debug)]
241pub enum FuncType {
242 Method,
243 MethodMut,
244 Associated,
245}
246
247impl From<FuncKind> for Func {
248 fn from(value: FuncKind) -> Self {
249 Self {
250 span: Span::detached(),
251 kind: value,
252 }
253 }
254}
255
256impl From<&'static NativeFuncData> for Func {
257 fn from(data: &'static NativeFuncData) -> Self {
258 FuncKind::Native(Static(data)).into()
259 }
260}
261
262impl From<Closure> for Func {
263 fn from(closure: Closure) -> Self {
264 FuncKind::Closure(Box::new(closure)).into()
265 }
266}
267
268#[derive(Debug)]
269pub struct ParamInfo {
270 pub name: &'static str,
271}
272
273cast! {
274 &'static NativeFuncData,
275 self => Func::from(self).into_value(),
276}