1use crate::{
15 builtins::{Array, BuiltIn},
16 context::StandardObjects,
17 environment::lexical_environment::Environment,
18 gc::{empty_trace, Finalize, Trace},
19 object::{
20 internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
21 JsObject, NativeObject, Object, ObjectData,
22 },
23 property::{Attribute, PropertyDescriptor},
24 syntax::ast::node::{FormalParameter, RcStatementList},
25 BoaProfiler, Context, JsResult, JsValue,
26};
27use bitflags::bitflags;
28use dyn_clone::DynClone;
29
30use sealed::Sealed;
31use std::fmt::{self, Debug};
32use std::ops::{Deref, DerefMut};
33
34use super::JsArgs;
35
36#[cfg(test)]
37mod tests;
38
39mod sealed {
43 pub trait Sealed {}
44 impl<T: Copy> Sealed for T {}
45}
46pub trait DynCopy: Sealed {}
47impl<T: Copy> DynCopy for T {}
48
49pub type NativeFunction = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
54
55pub trait ClosureFunction:
61 Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + DynCopy + DynClone + 'static
62{
63}
64
65impl<T> ClosureFunction for T where
67 T: Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + Copy + 'static
68{
69}
70
71dyn_clone::clone_trait_object!(ClosureFunction);
73
74#[derive(Clone, Copy, Finalize)]
75pub struct BuiltInFunction(pub(crate) NativeFunction);
76
77unsafe impl Trace for BuiltInFunction {
78 empty_trace!();
79}
80
81impl From<NativeFunction> for BuiltInFunction {
82 fn from(function: NativeFunction) -> Self {
83 Self(function)
84 }
85}
86
87impl Debug for BuiltInFunction {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str("[native]")
90 }
91}
92
93bitflags! {
94 #[derive(Finalize, Default)]
95 pub struct FunctionFlags: u8 {
96 const CONSTRUCTABLE = 0b0000_0010;
97 const LEXICAL_THIS_MODE = 0b0000_0100;
98 }
99}
100
101impl FunctionFlags {
102 #[inline]
103 pub(crate) fn is_constructable(&self) -> bool {
104 self.contains(Self::CONSTRUCTABLE)
105 }
106
107 #[inline]
108 pub(crate) fn is_lexical_this_mode(&self) -> bool {
109 self.contains(Self::LEXICAL_THIS_MODE)
110 }
111}
112
113unsafe impl Trace for FunctionFlags {
114 empty_trace!();
115}
116
117trait CapturesObject: NativeObject + DynClone {}
123impl<T: NativeObject + Clone> CapturesObject for T {}
124dyn_clone::clone_trait_object!(CapturesObject);
125
126#[derive(Debug, Clone, Trace, Finalize)]
138pub struct Captures(Box<dyn CapturesObject>);
139
140impl Captures {
141 pub(crate) fn new<T>(captures: T) -> Self
143 where
144 T: NativeObject + Clone,
145 {
146 Self(Box::new(captures))
147 }
148
149 pub fn downcast_ref<T>(&self) -> Option<&T>
152 where
153 T: NativeObject + Clone,
154 {
155 self.0.deref().as_any().downcast_ref::<T>()
156 }
157
158 pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
161 where
162 T: NativeObject + Clone,
163 {
164 self.0.deref_mut().as_mut_any().downcast_mut::<T>()
165 }
166
167 pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
170 where
171 T: NativeObject + Clone,
172 {
173 self.0
174 .deref()
175 .as_any()
176 .downcast_ref::<T>()
177 .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
178 }
179
180 pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
183 where
184 T: NativeObject + Clone,
185 {
186 self.0
187 .deref_mut()
188 .as_mut_any()
189 .downcast_mut::<T>()
190 .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
191 }
192}
193
194#[derive(Clone, Trace, Finalize)]
200pub enum Function {
201 Native {
202 function: BuiltInFunction,
203 constructable: bool,
204 },
205 Closure {
206 #[unsafe_ignore_trace]
207 function: Box<dyn ClosureFunction>,
208 constructable: bool,
209 captures: Captures,
210 },
211 Ordinary {
212 flags: FunctionFlags,
213 body: RcStatementList,
214 params: Box<[FormalParameter]>,
215 environment: Environment,
216 },
217}
218
219impl Debug for Function {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "Function {{ ... }}")
222 }
223}
224
225impl Function {
226 pub(crate) fn add_rest_param(
228 &self,
229 param: &FormalParameter,
230 index: usize,
231 args_list: &[JsValue],
232 context: &mut Context,
233 local_env: &Environment,
234 ) {
235 let array = Array::new_array(context);
237 Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context)
238 .unwrap();
239
240 local_env
242 .create_mutable_binding(param.name(), false, true, context)
244 .expect("Failed to create binding for rest param");
245
246 local_env
248 .initialize_binding(param.name(), array, context)
249 .expect("Failed to initialize rest param");
250 }
251
252 pub(crate) fn add_arguments_to_environment(
254 &self,
255 param: &FormalParameter,
256 value: JsValue,
257 local_env: &Environment,
258 context: &mut Context,
259 ) {
260 local_env
262 .create_mutable_binding(param.name(), false, true, context)
263 .expect("Failed to create binding");
264
265 local_env
267 .initialize_binding(param.name(), value, context)
268 .expect("Failed to intialize binding");
269 }
270
271 pub fn is_constructable(&self) -> bool {
273 match self {
274 Self::Native { constructable, .. } => *constructable,
275 Self::Closure { constructable, .. } => *constructable,
276 Self::Ordinary { flags, .. } => flags.is_constructable(),
277 }
278 }
279}
280
281pub fn create_unmapped_arguments_object(
285 arguments_list: &[JsValue],
286 context: &mut Context,
287) -> JsResult<JsValue> {
288 let len = arguments_list.len();
289 let obj = JsObject::new(Object::default());
290 let length = PropertyDescriptor::builder()
292 .value(len)
293 .writable(true)
294 .enumerable(false)
295 .configurable(true);
296 crate::object::internal_methods::ordinary_define_own_property(
298 &obj,
299 "length".into(),
300 length.into(),
301 context,
302 )?;
303 let mut index: usize = 0;
304 while index < len {
305 let val = arguments_list.get(index).expect("Could not get argument");
306 let prop = PropertyDescriptor::builder()
307 .value(val.clone())
308 .writable(true)
309 .enumerable(true)
310 .configurable(true);
311
312 obj.insert(index, prop);
313 index += 1;
314 }
315
316 Ok(JsValue::new(obj))
317}
318
319pub fn make_builtin_fn<N>(
338 function: NativeFunction,
339 name: N,
340 parent: &JsObject,
341 length: usize,
342 interpreter: &Context,
343) where
344 N: Into<String>,
345{
346 let name = name.into();
347 let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
348
349 let mut function = Object::function(
350 Function::Native {
351 function: function.into(),
352 constructable: false,
353 },
354 interpreter
355 .standard_objects()
356 .function_object()
357 .prototype()
358 .into(),
359 );
360 let attribute = PropertyDescriptor::builder()
361 .writable(false)
362 .enumerable(false)
363 .configurable(true);
364 function.insert_property("length", attribute.clone().value(length));
365 function.insert_property("name", attribute.value(name.as_str()));
366
367 parent.clone().insert_property(
368 name,
369 PropertyDescriptor::builder()
370 .value(function)
371 .writable(true)
372 .enumerable(false)
373 .configurable(true),
374 );
375}
376
377#[derive(Debug, Clone, Copy)]
378pub struct BuiltInFunctionObject;
379
380impl BuiltInFunctionObject {
381 pub const LENGTH: usize = 1;
382
383 fn constructor(
384 new_target: &JsValue,
385 _: &[JsValue],
386 context: &mut Context,
387 ) -> JsResult<JsValue> {
388 let prototype =
389 get_prototype_from_constructor(new_target, StandardObjects::function_object, context)?;
390 let this = JsValue::new_object(context);
391
392 this.as_object()
393 .expect("this should be an object")
394 .set_prototype_instance(prototype.into());
395
396 this.set_data(ObjectData::function(Function::Native {
397 function: BuiltInFunction(|_, _, _| Ok(JsValue::undefined())),
398 constructable: true,
399 }));
400 Ok(this)
401 }
402
403 fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
404 Ok(JsValue::undefined())
405 }
406
407 fn call(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
418 if !this.is_function() {
419 return context.throw_type_error(format!("{} is not a function", this.display()));
420 }
421 let this_arg = args.get_or_undefined(0);
422 let start = if !args.is_empty() { 1 } else { 0 };
424 context.call(this, this_arg, &args[start..])
425 }
426
427 fn apply(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
439 if !this.is_function() {
440 return context.throw_type_error(format!("{} is not a function", this.display()));
441 }
442 let this_arg = args.get_or_undefined(0);
443 let arg_array = args.get_or_undefined(1);
444 if arg_array.is_null_or_undefined() {
445 return context.call(this, this_arg, &[]);
447 }
448 let arg_list = arg_array.create_list_from_array_like(&[], context)?;
449 context.call(this, this_arg, &arg_list)
451 }
452
453 #[allow(clippy::wrong_self_convention)]
454 fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
455 let name = {
456 let value = this.get_field("name", &mut *context)?;
459 if value.is_null_or_undefined() {
460 None
461 } else {
462 Some(value.to_string(context)?)
463 }
464 };
465
466 let function = {
467 let object = this
468 .as_object()
469 .map(|object| object.borrow().as_function().cloned());
470
471 if let Some(Some(function)) = object {
472 function
473 } else {
474 return context.throw_type_error("Not a function");
475 }
476 };
477
478 match (&function, name) {
479 (
480 Function::Native {
481 function: _,
482 constructable: _,
483 },
484 Some(name),
485 ) => Ok(format!("function {}() {{\n [native Code]\n}}", &name).into()),
486 (Function::Ordinary { body, params, .. }, Some(name)) => {
487 let arguments: String = params
488 .iter()
489 .map(|param| param.name())
490 .collect::<Vec<&str>>()
491 .join(", ");
492
493 let statement_list = &*body;
494 let is_multiline = {
498 let value = statement_list.to_string();
499 value.lines().count() > 1
500 };
501 if is_multiline {
502 Ok(
503 format!(
506 "{}({}) {{\n{}}}",
507 &name,
508 arguments,
509 statement_list.to_string()
510 )
511 .into(),
512 )
513 } else {
514 Ok(format!(
515 "{}({}) {{{}}}",
516 &name,
517 arguments,
518 statement_list.to_string().trim()
521 )
522 .into())
523 }
524 }
525
526 _ => Ok("TODO".into()),
527 }
528 }
529}
530
531impl BuiltIn for BuiltInFunctionObject {
532 const NAME: &'static str = "Function";
533
534 fn attribute() -> Attribute {
535 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
536 }
537
538 fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
539 let _timer = BoaProfiler::global().start_event("function", "init");
540
541 let function_prototype = context.standard_objects().function_object().prototype();
542 FunctionBuilder::native(context, Self::prototype)
543 .name("")
544 .length(0)
545 .constructable(false)
546 .build_function_prototype(&function_prototype);
547
548 let function_object = ConstructorBuilder::with_standard_object(
549 context,
550 Self::constructor,
551 context.standard_objects().function_object().clone(),
552 )
553 .name(Self::NAME)
554 .length(Self::LENGTH)
555 .method(Self::call, "call", 1)
556 .method(Self::apply, "apply", 1)
557 .method(Self::to_string, "toString", 0)
558 .build();
559
560 (Self::NAME, function_object.into(), Self::attribute())
561 }
562}