boa/builtins/function/
mod.rs

1//! This module implements the global `Function` object as well as creates Native Functions.
2//!
3//! Objects wrap `Function`s and expose them via call/construct slots.
4//!
5//! `The `Function` object is used for matching text with a pattern.
6//!
7//! More information:
8//!  - [ECMAScript reference][spec]
9//!  - [MDN documentation][mdn]
10//!
11//! [spec]: https://tc39.es/ecma262/#sec-function-objects
12//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
13
14use 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
39// Allows restricting closures to only `Copy` ones.
40// Used the sealed pattern to disallow external implementations
41// of `DynCopy`.
42mod sealed {
43    pub trait Sealed {}
44    impl<T: Copy> Sealed for T {}
45}
46pub trait DynCopy: Sealed {}
47impl<T: Copy> DynCopy for T {}
48
49/// Type representing a native built-in function a.k.a. function pointer.
50///
51/// Native functions need to have this signature in order to
52/// be callable from Javascript.
53pub type NativeFunction = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
54
55/// Trait representing a native built-in closure.
56///
57/// Closures need to have this signature in order to
58/// be callable from Javascript, but most of the time the compiler
59/// is smart enough to correctly infer the types.
60pub trait ClosureFunction:
61    Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + DynCopy + DynClone + 'static
62{
63}
64
65// The `Copy` bound automatically infers `DynCopy` and `DynClone`
66impl<T> ClosureFunction for T where
67    T: Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + Copy + 'static
68{
69}
70
71// Allows cloning Box<dyn ClosureFunction>
72dyn_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
117// We don't use a standalone `NativeObject` for `Captures` because it doesn't
118// guarantee that the internal type implements `Clone`.
119// This private trait guarantees that the internal type passed to `Captures`
120// implements `Clone`, and `DynClone` allows us to implement `Clone` for
121// `Box<dyn CapturesObject>`.
122trait CapturesObject: NativeObject + DynClone {}
123impl<T: NativeObject + Clone> CapturesObject for T {}
124dyn_clone::clone_trait_object!(CapturesObject);
125
126/// Wrapper for `Box<dyn NativeObject + Clone>` that allows passing additional
127/// captures through a `Copy` closure.
128///
129/// Any type implementing `Trace + Any + Debug + Clone`
130/// can be used as a capture context, so you can pass e.g. a String,
131/// a tuple or even a full struct.
132///
133/// You can downcast to any type and handle the fail case as you like
134/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
135/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
136/// fails.
137#[derive(Debug, Clone, Trace, Finalize)]
138pub struct Captures(Box<dyn CapturesObject>);
139
140impl Captures {
141    /// Creates a new capture context.
142    pub(crate) fn new<T>(captures: T) -> Self
143    where
144        T: NativeObject + Clone,
145    {
146        Self(Box::new(captures))
147    }
148
149    /// Downcasts `Captures` to the specified type, returning a reference to the
150    /// downcasted type if successful or `None` otherwise.
151    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    /// Mutably downcasts `Captures` to the specified type, returning a
159    /// mutable reference to the downcasted type if successful or `None` otherwise.
160    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    /// Downcasts `Captures` to the specified type, returning a reference to the
168    /// downcasted type if successful or a `TypeError` otherwise.
169    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    /// Downcasts `Captures` to the specified type, returning a reference to the
181    /// downcasted type if successful or a `TypeError` otherwise.
182    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/// Boa representation of a Function Object.
195///
196/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
197///
198/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
199#[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    // Adds the final rest parameters to the Environment as an array
227    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        // Create array of values
236        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        // Create binding
241        local_env
242            // Function parameters can share names in JavaScript...
243            .create_mutable_binding(param.name(), false, true, context)
244            .expect("Failed to create binding for rest param");
245
246        // Set Binding to value
247        local_env
248            .initialize_binding(param.name(), array, context)
249            .expect("Failed to initialize rest param");
250    }
251
252    // Adds an argument to the environment
253    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        // Create binding
261        local_env
262            .create_mutable_binding(param.name(), false, true, context)
263            .expect("Failed to create binding");
264
265        // Set Binding to value
266        local_env
267            .initialize_binding(param.name(), value, context)
268            .expect("Failed to intialize binding");
269    }
270
271    /// Returns true if the function object is constructable.
272    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
281/// Arguments.
282///
283/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
284pub 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    // Set length
291    let length = PropertyDescriptor::builder()
292        .value(len)
293        .writable(true)
294        .enumerable(false)
295        .configurable(true);
296    // Define length as a property
297    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
319/// Creates a new member function of a `Object` or `prototype`.
320///
321/// A function registered using this macro can then be called from Javascript using:
322///
323/// parent.name()
324///
325/// See the javascript 'Number.toString()' as an example.
326///
327/// # Arguments
328/// function: The function to register as a built in function.
329/// name: The name of the function (how it will be called but without the ()).
330/// parent: The object to register the function on, if the global object is used then the function is instead called as name()
331///     without requiring the parent, see parseInt() as an example.
332/// length: As described at <https://tc39.es/ecma262/#sec-function-instances-length>, The value of the "length" property is an integer that
333///     indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with
334///     some other number of arguments.
335///
336/// If no length is provided, the length will be set to 0.
337pub 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    /// `Function.prototype.call`
408    ///
409    /// The call() method invokes self with the first argument as the `this` value.
410    ///
411    /// More information:
412    ///  - [MDN documentation][mdn]
413    ///  - [ECMAScript reference][spec]
414    ///
415    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call
416    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
417    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        // TODO?: 3. Perform PrepareForTailCall
423        let start = if !args.is_empty() { 1 } else { 0 };
424        context.call(this, this_arg, &args[start..])
425    }
426
427    /// `Function.prototype.apply`
428    ///
429    /// The apply() method invokes self with the first argument as the `this` value
430    /// and the rest of the arguments provided as an array (or an array-like object).
431    ///
432    /// More information:
433    ///  - [MDN documentation][mdn]
434    ///  - [ECMAScript reference][spec]
435    ///
436    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.apply
437    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
438    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            // TODO?: 3.a. PrepareForTailCall
446            return context.call(this, this_arg, &[]);
447        }
448        let arg_list = arg_array.create_list_from_array_like(&[], context)?;
449        // TODO?: 5. PrepareForTailCall
450        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            // Is there a case here where if there is no name field on a value
457            // name should default to None? Do all functions have names set?
458            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                // This is a kluge. The implementaion in browser seems to suggest that
495                // the value here is printed exactly as defined in source. I'm not sure if
496                // that's possible here, but for now here's a dumb heuristic that prints functions
497                let is_multiline = {
498                    let value = statement_list.to_string();
499                    value.lines().count() > 1
500                };
501                if is_multiline {
502                    Ok(
503                        // ?? For some reason statement_list string implementation
504                        // sticks a \n at the end no matter what
505                        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                        // The trim here is to remove a \n stuck at the end
519                        // of the statement_list to_string method
520                        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}