Skip to main content

boa_engine/builtins/function/
mod.rs

1//! Boa's implementation of ECMAScript's global `Function` object and 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    Context, JsArgs, JsExpect, JsResult, JsStr, JsString, JsValue, SpannedSourceText,
16    builtins::{
17        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject,
18    },
19    bytecompiler::FunctionCompiler,
20    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
21    environments::{EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus},
22    error::JsNativeError,
23    js_error, js_string,
24    native_function::NativeFunctionObject,
25    object::{
26        JsData, JsFunction, JsObject, PrivateElement, PrivateName,
27        internal_methods::{
28            CallValue, InternalMethodCallContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
29            get_prototype_from_constructor,
30        },
31    },
32    property::{Attribute, PropertyDescriptor, PropertyKey},
33    realm::Realm,
34    string::StaticJsStrings,
35    symbol::JsSymbol,
36    value::IntegerOrInfinity,
37    vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
38};
39use boa_ast::{
40    Position, Span, Spanned, StatementList,
41    function::{FormalParameterList, FunctionBody},
42    operations::{
43        ContainsSymbol, all_private_identifiers_valid, bound_names, contains,
44        lexically_declared_names,
45    },
46    scope::BindingLocatorScope,
47};
48use boa_gc::{self, Finalize, Gc, Trace, custom_trace};
49use boa_interner::Sym;
50use boa_macros::js_str;
51use boa_parser::{Parser, Source};
52use thin_vec::ThinVec;
53
54use super::Proxy;
55
56pub(crate) mod arguments;
57mod bound;
58
59pub use bound::BoundFunction;
60
61#[cfg(test)]
62mod tests;
63
64/// Represents the `[[ThisMode]]` internal slot of function objects.
65///
66/// More information:
67///  - [ECMAScript specification][spec]
68///
69/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
70#[derive(Debug, Trace, Finalize, PartialEq, Eq, Clone)]
71pub enum ThisMode {
72    /// The `this` value refers to the `this` value of a lexically enclosing function.
73    Lexical,
74
75    /// The `this` value is used exactly as provided by an invocation of the function.
76    Strict,
77
78    /// The `this` value of `undefined` or `null` is interpreted as a reference to the global object,
79    /// and any other `this` value is first passed to `ToObject`.
80    Global,
81}
82
83impl ThisMode {
84    /// Returns `true` if the this mode is `Lexical`.
85    #[must_use]
86    pub const fn is_lexical(&self) -> bool {
87        matches!(self, Self::Lexical)
88    }
89
90    /// Returns `true` if the this mode is `Strict`.
91    #[must_use]
92    pub const fn is_strict(&self) -> bool {
93        matches!(self, Self::Strict)
94    }
95
96    /// Returns `true` if the this mode is `Global`.
97    #[must_use]
98    pub const fn is_global(&self) -> bool {
99        matches!(self, Self::Global)
100    }
101}
102
103/// Represents the `[[ConstructorKind]]` internal slot of function objects.
104///
105/// More information:
106///  - [ECMAScript specification][spec]
107///
108/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub enum ConstructorKind {
111    /// The class constructor is not derived.
112    Base,
113
114    /// The class constructor is a derived class constructor.
115    Derived,
116}
117
118impl ConstructorKind {
119    /// Returns `true` if the constructor kind is `Base`.
120    #[must_use]
121    pub const fn is_base(&self) -> bool {
122        matches!(self, Self::Base)
123    }
124
125    /// Returns `true` if the constructor kind is `Derived`.
126    #[must_use]
127    pub const fn is_derived(&self) -> bool {
128        matches!(self, Self::Derived)
129    }
130}
131
132/// Record containing the field definition of classes.
133///
134/// More information:
135///  - [ECMAScript specification][spec]
136///
137/// [spec]: https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type
138#[derive(Clone, Debug, Finalize)]
139pub enum ClassFieldDefinition {
140    /// A class field definition with a `string` or `symbol` as a name.
141    Public(PropertyKey, JsFunction, Option<PropertyKey>),
142
143    /// A class field definition with a private name.
144    Private(PrivateName, JsFunction),
145}
146
147unsafe impl Trace for ClassFieldDefinition {
148    custom_trace! {this, mark, {
149        match this {
150            Self::Public(_key, func, _) => {
151                mark(func);
152            }
153            Self::Private(_, func) => {
154                mark(func);
155            }
156        }
157    }}
158}
159
160/// Boa representation of a JavaScript Function Object.
161///
162/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
163/// (AST Node).
164///
165/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
166#[derive(Debug, Trace, Finalize)]
167pub struct OrdinaryFunction {
168    /// The code block containing the compiled function.
169    pub(crate) code: Gc<CodeBlock>,
170
171    /// The `[[Environment]]` internal slot.
172    pub(crate) environments: EnvironmentStack,
173
174    /// The `[[HomeObject]]` internal slot.
175    pub(crate) home_object: Option<JsObject>,
176
177    /// The `[[ScriptOrModule]]` internal slot.
178    pub(crate) script_or_module: Option<ActiveRunnable>,
179
180    /// The [`Realm`] the function is defined in.
181    pub(crate) realm: Realm,
182
183    /// The `[[Fields]]` internal slot.
184    fields: ThinVec<ClassFieldDefinition>,
185
186    /// The `[[PrivateMethods]]` internal slot.
187    private_methods: ThinVec<(PrivateName, PrivateElement)>,
188}
189
190impl JsData for OrdinaryFunction {
191    fn internal_methods(&self) -> &'static InternalObjectMethods {
192        static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods {
193            __call__: function_call,
194            ..ORDINARY_INTERNAL_METHODS
195        };
196
197        static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods {
198            __call__: function_call,
199            __construct__: function_construct,
200            ..ORDINARY_INTERNAL_METHODS
201        };
202
203        if self.code.has_prototype_property() {
204            &CONSTRUCTOR_METHODS
205        } else {
206            &FUNCTION_METHODS
207        }
208    }
209}
210
211impl OrdinaryFunction {
212    pub(crate) fn new(
213        code: Gc<CodeBlock>,
214        environments: EnvironmentStack,
215        script_or_module: Option<ActiveRunnable>,
216        realm: Realm,
217    ) -> Self {
218        Self {
219            code,
220            environments,
221            home_object: None,
222            script_or_module,
223            realm,
224            fields: ThinVec::default(),
225            private_methods: ThinVec::default(),
226        }
227    }
228
229    /// Returns the codeblock of the function.
230    #[must_use]
231    pub fn codeblock(&self) -> &CodeBlock {
232        &self.code
233    }
234
235    /// Push a private environment to the function.
236    pub(crate) fn push_private_environment(&mut self, environment: Gc<PrivateEnvironment>) {
237        self.environments.push_private(environment);
238    }
239
240    /// Returns true if the function object is a derived constructor.
241    pub(crate) fn is_derived_constructor(&self) -> bool {
242        self.code.is_derived_constructor()
243    }
244
245    /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
246    pub(crate) fn in_class_field_initializer(&self) -> bool {
247        self.code.in_class_field_initializer()
248    }
249
250    /// Returns a reference to the function `[[HomeObject]]` slot if present.
251    pub(crate) const fn get_home_object(&self) -> Option<&JsObject> {
252        self.home_object.as_ref()
253    }
254
255    ///  Sets the `[[HomeObject]]` slot if present.
256    pub(crate) fn set_home_object(&mut self, object: JsObject) {
257        self.home_object = Some(object);
258    }
259
260    /// Returns the values of the `[[Fields]]` internal slot.
261    pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] {
262        &self.fields
263    }
264
265    /// Pushes a value to the `[[Fields]]` internal slot if present.
266    pub(crate) fn push_field(
267        &mut self,
268        key: PropertyKey,
269        value: JsFunction,
270        function_name: Option<PropertyKey>,
271    ) {
272        self.fields
273            .push(ClassFieldDefinition::Public(key, value, function_name));
274    }
275
276    /// Pushes a private value to the `[[Fields]]` internal slot if present.
277    pub(crate) fn push_field_private(&mut self, name: PrivateName, value: JsFunction) {
278        self.fields.push(ClassFieldDefinition::Private(name, value));
279    }
280
281    /// Returns the values of the `[[PrivateMethods]]` internal slot.
282    pub(crate) fn get_private_methods(&self) -> &[(PrivateName, PrivateElement)] {
283        &self.private_methods
284    }
285
286    /// Pushes a private method to the `[[PrivateMethods]]` internal slot if present.
287    pub(crate) fn push_private_method(&mut self, name: PrivateName, method: PrivateElement) {
288        self.private_methods.push((name, method));
289    }
290
291    /// Gets the `Realm` from where this function originates.
292    #[must_use]
293    pub const fn realm(&self) -> &Realm {
294        &self.realm
295    }
296
297    /// Checks if this function is an ordinary function.
298    pub(crate) fn is_ordinary(&self) -> bool {
299        self.code.is_ordinary()
300    }
301}
302
303/// The internal representation of a `Function` object.
304#[derive(Debug, Clone, Copy)]
305pub struct BuiltInFunctionObject;
306
307impl IntrinsicObject for BuiltInFunctionObject {
308    fn init(realm: &Realm) {
309        let has_instance = BuiltInBuilder::callable(realm, Self::has_instance)
310            .name(js_string!("[Symbol.hasInstance]"))
311            .length(1)
312            .build();
313
314        let throw_type_error = realm.intrinsics().objects().throw_type_error();
315
316        BuiltInBuilder::from_standard_constructor::<Self>(realm)
317            .method(Self::apply, js_string!("apply"), 2)
318            .method(Self::bind, js_string!("bind"), 1)
319            .method(Self::call, js_string!("call"), 1)
320            .method(Self::to_string, js_string!("toString"), 0)
321            .property(JsSymbol::has_instance(), has_instance, Attribute::default())
322            .accessor(
323                js_string!("caller"),
324                Some(throw_type_error.clone()),
325                Some(throw_type_error.clone()),
326                Attribute::CONFIGURABLE,
327            )
328            .accessor(
329                js_string!("arguments"),
330                Some(throw_type_error.clone()),
331                Some(throw_type_error),
332                Attribute::CONFIGURABLE,
333            )
334            .build();
335
336        let prototype = realm.intrinsics().constructors().function().prototype();
337
338        BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype)
339            .name(js_string!())
340            .length(0)
341            .build();
342
343        prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype()));
344    }
345
346    fn get(intrinsics: &Intrinsics) -> JsObject {
347        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
348    }
349}
350
351impl BuiltInObject for BuiltInFunctionObject {
352    const NAME: JsString = StaticJsStrings::FUNCTION;
353}
354
355impl BuiltInConstructor for BuiltInFunctionObject {
356    const CONSTRUCTOR_ARGUMENTS: usize = 1;
357    const PROTOTYPE_STORAGE_SLOTS: usize = 10;
358    const CONSTRUCTOR_STORAGE_SLOTS: usize = 0;
359
360    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
361        StandardConstructors::function;
362
363    /// `Function ( p1, p2, … , pn, body )`
364    ///
365    /// The `apply()` method invokes self with the first argument as the `this` value
366    /// and the rest of the arguments provided as an array (or an array-like object).
367    ///
368    /// More information:
369    ///  - [MDN documentation][mdn]
370    ///  - [ECMAScript reference][spec]
371    ///
372    /// [spec]: https://tc39.es/ecma262/#sec-function-p1-p2-pn-body
373    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function
374    fn constructor(
375        new_target: &JsValue,
376        args: &[JsValue],
377        context: &mut Context,
378    ) -> JsResult<JsValue> {
379        let active_function = context
380            .active_function_object()
381            .unwrap_or_else(|| context.intrinsics().constructors().function().constructor());
382        Self::create_dynamic_function(active_function, new_target, args, false, false, context)
383            .map(Into::into)
384    }
385}
386
387impl BuiltInFunctionObject {
388    /// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
389    ///
390    /// More information:
391    ///  - [ECMAScript reference][spec]
392    ///
393    /// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
394    pub(crate) fn create_dynamic_function(
395        constructor: JsObject,
396        new_target: &JsValue,
397        args: &[JsValue],
398        r#async: bool,
399        generator: bool,
400        context: &mut Context,
401    ) -> JsResult<JsObject> {
402        // 1. If newTarget is undefined, set newTarget to constructor.
403        let new_target = if new_target.is_undefined() {
404            constructor.into()
405        } else {
406            new_target.clone()
407        };
408
409        let strict = context.is_strict();
410
411        let default = if r#async && generator {
412            // 5. Else,
413            //     a. Assert: kind is async-generator.
414            //     b. Let prefix be "async function*".
415            //     c. Let exprSym be the grammar symbol AsyncGeneratorExpression.
416            //     d. Let bodySym be the grammar symbol AsyncGeneratorBody.
417            //     e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await].
418            //     f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
419            StandardConstructors::async_generator_function
420        } else if r#async {
421            // 4. Else if kind is async, then
422            //     a. Let prefix be "async function".
423            //     b. Let exprSym be the grammar symbol AsyncFunctionExpression.
424            //     c. Let bodySym be the grammar symbol AsyncFunctionBody.
425            //     d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await].
426            //     e. Let fallbackProto be "%AsyncFunction.prototype%".
427            StandardConstructors::async_function
428        } else if generator {
429            // 3. Else if kind is generator, then
430            //     a. Let prefix be "function*".
431            //     b. Let exprSym be the grammar symbol GeneratorExpression.
432            //     c. Let bodySym be the grammar symbol GeneratorBody.
433            //     d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await].
434            //     e. Let fallbackProto be "%GeneratorFunction.prototype%".
435            StandardConstructors::generator_function
436        } else {
437            // 2. If kind is normal, then
438            //     a. Let prefix be "function".
439            //     b. Let exprSym be the grammar symbol FunctionExpression.
440            //     c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
441            //     d. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await].
442            //     e. Let fallbackProto be "%Function.prototype%".
443            StandardConstructors::function
444        };
445
446        // 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
447        let prototype = get_prototype_from_constructor(&new_target, default, context)?;
448
449        // 6. Let argCount be the number of elements in parameterArgs.
450        let (body, param_list) = if let Some((body, params)) = args.split_last() {
451            // 7. Let parameterStrings be a new empty List.
452            let mut parameters = Vec::with_capacity(args.len());
453
454            // 8. For each element arg of parameterArgs, do
455            for param in params {
456                // a. Append ? ToString(arg) to parameterStrings.
457                parameters.push(param.to_string(context)?);
458            }
459
460            // 9. Let bodyString be ? ToString(bodyArg).
461            let body = body.to_string(context)?;
462
463            (body, parameters)
464        } else {
465            (js_string!(), Vec::new())
466        };
467        let current_realm = context.realm().clone();
468
469        context.host_hooks().ensure_can_compile_strings(
470            current_realm,
471            &param_list,
472            &body,
473            false,
474            context,
475        )?;
476
477        let parameters = if param_list.is_empty() {
478            FormalParameterList::default()
479        } else {
480            // 12. Let P be the empty String.
481            // 13. If argCount > 0, then
482            //     a. Set P to parameterStrings[0].
483            //     b. Let k be 1.
484            //     c. Repeat, while k < argCount,
485            //         i. Let nextArgString be parameterStrings[k].
486            //         ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
487            //         iii. Set k to k + 1.
488
489            // TODO: Replace with standard `Iterator::intersperse` iterator method when it's stabilized.
490            //       See: <https://github.com/rust-lang/rust/issues/79524>
491            let parameters = itertools::Itertools::intersperse(
492                param_list.iter().map(JsString::iter),
493                js_str!(",").iter(),
494            )
495            .flatten()
496            .collect::<Vec<_>>();
497            let mut parser = Parser::new(Source::from_utf16(&parameters));
498            parser.set_identifier(context.next_parser_identifier());
499
500            // 17. Let parameters be ParseText(StringToCodePoints(P), parameterSym).
501            // 18. If parameters is a List of errors, throw a SyntaxError exception.
502            let parameters = parser
503                .parse_formal_parameters(context.interner_mut(), generator, r#async)
504                .map_err(|e| {
505                    JsNativeError::syntax()
506                        .with_message(format!("failed to parse function parameters: {e}"))
507                })?;
508
509            // It is a Syntax Error if FormalParameters Contains YieldExpression is true.
510            if generator && contains(&parameters, ContainsSymbol::YieldExpression) {
511                return Err(JsNativeError::syntax().with_message(
512                    if r#async {
513                        "yield expression is not allowed in formal parameter list of async generator"
514                    } else {
515                        "yield expression is not allowed in formal parameter list of generator"
516                    }
517                ).into());
518            }
519
520            // It is a Syntax Error if FormalParameters Contains AwaitExpression is true.
521            if r#async && contains(&parameters, ContainsSymbol::AwaitExpression) {
522                return Err(JsNativeError::syntax()
523                    .with_message(
524                        if generator {
525                            "await expression is not allowed in formal parameter list of async function"
526                        } else {
527                            "await expression is not allowed in formal parameter list of async generator"
528                        })
529                    .into());
530            }
531
532            parameters
533        };
534
535        let body = if body.is_empty() {
536            FunctionBody::new(StatementList::default(), Span::new((1, 1), (1, 1)))
537        } else {
538            // 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
539            let mut body_parse = Vec::with_capacity(body.len());
540            body_parse.push(u16::from(b'\n'));
541            body_parse.extend(body.iter());
542            body_parse.push(u16::from(b'\n'));
543
544            // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
545            // 20. If body is a List of errors, throw a SyntaxError exception.
546            let mut parser = Parser::new(Source::from_utf16(&body_parse));
547            parser.set_identifier(context.next_parser_identifier());
548
549            // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
550            // 20. If body is a List of errors, throw a SyntaxError exception.
551            let body = parser
552                .parse_function_body(context.interner_mut(), generator, r#async)
553                .map_err(|e| {
554                    JsNativeError::syntax()
555                        .with_message(format!("failed to parse function body: {e}"))
556                })?;
557
558            // It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with argument « »
559            // is false unless the source text containing ScriptBody is eval code that is being
560            // processed by a direct eval.
561            // https://tc39.es/ecma262/#sec-scripts-static-semantics-early-errors
562            if !all_private_identifiers_valid(&body, Vec::new()) {
563                return Err(JsNativeError::syntax()
564                    .with_message("invalid private identifier usage")
565                    .into());
566            }
567
568            // 21. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") does not evaluate to a function.
569            // 22. NOTE: If this step is reached, sourceText must have the syntax of exprSym (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to exprSym directly.
570            // 23. Let expr be ParseText(sourceText, exprSym).
571            // 24. If expr is a List of errors, throw a SyntaxError exception.
572            // Check for errors that apply for the combination of body and parameters.
573
574            // Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
575            // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
576            if body.strict() {
577                for name in bound_names(&parameters) {
578                    if name == Sym::ARGUMENTS || name == Sym::EVAL {
579                        return Err(JsNativeError::syntax()
580                            .with_message("Unexpected 'eval' or 'arguments' in strict mode")
581                            .into());
582                    }
583                }
584            }
585
586            // Early Error: If the source code matching FormalParameters is strict mode code,
587            // the Early Error rules for UniqueFormalParameters : FormalParameters are applied.
588            if (body.strict()) && parameters.has_duplicates() {
589                return Err(JsNativeError::syntax()
590                    .with_message("Duplicate parameter name not allowed in this context")
591                    .into());
592            }
593
594            // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true
595            // and IsSimpleParameterList of FormalParameters is false.
596            if body.strict() && !parameters.is_simple() {
597                return Err(JsNativeError::syntax()
598                    .with_message(
599                        "Illegal 'use strict' directive in function with non-simple parameter list",
600                    )
601                    .into());
602            }
603
604            // It is a Syntax Error if FunctionBody Contains SuperProperty is true.
605            if contains(&body, ContainsSymbol::SuperProperty) {
606                return Err(JsNativeError::syntax()
607                    .with_message("invalid `super` reference")
608                    .into());
609            }
610
611            // It is a Syntax Error if FunctionBody Contains SuperCall is true.
612            if contains(&body, ContainsSymbol::SuperCall) {
613                return Err(JsNativeError::syntax()
614                    .with_message("invalid `super` call")
615                    .into());
616            }
617
618            // It is a Syntax Error if any element of the BoundNames of FormalParameters
619            // also occurs in the LexicallyDeclaredNames of FunctionBody.
620            // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
621            {
622                let lexically_declared_names = lexically_declared_names(&body);
623                for name in bound_names(&parameters) {
624                    if lexically_declared_names.contains(&name) {
625                        return Err(JsNativeError::syntax()
626                            .with_message(format!(
627                                "Redeclaration of formal parameter `{}`",
628                                context.interner().resolve_expect(name)
629                            ))
630                            .into());
631                    }
632                }
633            }
634
635            body
636        };
637
638        // TODO: create SourceText : "anonymous(" parameters \n ") {" body_parse "}"
639
640        let function_span_start = Position::new(1, 1);
641        let function_span_end = body.span().end();
642        let mut function = boa_ast::function::FunctionExpression::new(
643            None,
644            parameters,
645            body,
646            None,
647            false,
648            Span::new(function_span_start, function_span_end),
649        );
650        if let Err(reason) =
651            function.analyze_scope(strict, context.realm().scope(), context.interner())
652        {
653            return Err(js_error!(SyntaxError: "failed to analyze function scope: {}", reason));
654        }
655
656        let in_with = context.vm.frame().environments.has_object_environment();
657        let spanned_source_text = SpannedSourceText::new_empty();
658
659        let code = FunctionCompiler::new(spanned_source_text)
660            .name(js_string!("anonymous"))
661            .generator(generator)
662            .r#async(r#async)
663            .in_with(in_with)
664            .force_function_scope(true)
665            .compile(
666                function.parameters(),
667                function.body(),
668                context.realm().scope().clone(),
669                context.realm().scope().clone(),
670                function.scopes(),
671                function.contains_direct_eval(),
672                context.interner_mut(),
673            );
674
675        let saved = context.vm.frame_mut().environments.pop_to_global();
676        let function_object = crate::vm::create_function_object(code, prototype, context);
677        context
678            .vm
679            .frame_mut()
680            .environments
681            .restore_from_saved(saved);
682
683        Ok(function_object)
684    }
685
686    /// `Function.prototype.apply ( thisArg, argArray )`
687    ///
688    /// The `apply()` method invokes self with the first argument as the `this` value
689    /// and the rest of the arguments provided as an array (or an array-like object).
690    ///
691    /// More information:
692    ///  - [MDN documentation][mdn]
693    ///  - [ECMAScript reference][spec]
694    ///
695    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.apply
696    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
697    fn apply(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
698        // 1. Let func be the this value.
699        // 2. If IsCallable(func) is false, throw a TypeError exception.
700        let func = this.as_callable().ok_or_else(|| {
701            JsNativeError::typ().with_message(format!("{} is not a function", this.display()))
702        })?;
703
704        let this_arg = args.get_or_undefined(0);
705        let arg_array = args.get_or_undefined(1);
706        // 3. If argArray is undefined or null, then
707        if arg_array.is_null_or_undefined() {
708            // a. Perform PrepareForTailCall().
709            // TODO?: 3.a. PrepareForTailCall
710
711            // b. Return ? Call(func, thisArg).
712            return func.call(this_arg, &[], context);
713        }
714
715        // 4. Let argList be ? CreateListFromArrayLike(argArray).
716        let arg_list = arg_array.create_list_from_array_like(&[], context)?;
717
718        // 5. Perform PrepareForTailCall().
719        // TODO?: 5. PrepareForTailCall
720
721        // 6. Return ? Call(func, thisArg, argList).
722        func.call(this_arg, &arg_list, context)
723    }
724
725    /// `Function.prototype.bind ( thisArg, ...args )`
726    ///
727    /// The `bind()` method creates a new function that, when called, has its
728    /// this keyword set to the provided value, with a given sequence of arguments
729    /// preceding any provided when the new function is called.
730    ///
731    /// More information:
732    ///  - [MDN documentation][mdn]
733    ///  - [ECMAScript reference][spec]
734    ///
735    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.bind
736    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
737    fn bind(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
738        // 1. Let Target be the this value.
739        // 2. If IsCallable(Target) is false, throw a TypeError exception.
740        let target = this.as_callable().ok_or_else(|| {
741            JsNativeError::typ()
742                .with_message("cannot bind `this` without a `[[Call]]` internal method")
743        })?;
744
745        let this_arg = args.get_or_undefined(0).clone();
746        let bound_args = args.get(1..).unwrap_or(&[]).to_vec();
747        let arg_count = bound_args.len() as i64;
748
749        // 3. Let F be ? BoundFunctionCreate(Target, thisArg, args).
750        let f = BoundFunction::create(target.clone(), this_arg, bound_args, context)?;
751
752        // 4. Let L be 0.
753        let mut l = JsValue::new(0);
754
755        // 5. Let targetHasLength be ? HasOwnProperty(Target, "length").
756        // 6. If targetHasLength is true, then
757        if target.has_own_property(StaticJsStrings::LENGTH, context)? {
758            // a. Let targetLen be ? Get(Target, "length").
759            let target_len = target.get(StaticJsStrings::LENGTH, context)?;
760            // b. If Type(targetLen) is Number, then
761            if target_len.is_number() {
762                // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen).
763                match target_len
764                    .to_integer_or_infinity(context)
765                    .js_expect("to_integer_or_infinity cannot fail for a number")?
766                {
767                    // i. If targetLen is +∞𝔽, set L to +∞.
768                    IntegerOrInfinity::PositiveInfinity => l = f64::INFINITY.into(),
769                    // ii. Else if targetLen is -∞𝔽, set L to 0.
770                    IntegerOrInfinity::NegativeInfinity => {}
771                    // iii. Else,
772                    IntegerOrInfinity::Integer(target_len) => {
773                        // 2. Assert: targetLenAsInt is finite.
774                        // 3. Let argCount be the number of elements in args.
775                        // 4. Set L to max(targetLenAsInt - argCount, 0).
776                        l = (target_len - arg_count).max(0).into();
777                    }
778                }
779            }
780        }
781
782        // 7. Perform ! SetFunctionLength(F, L).
783        f.define_property_or_throw(
784            StaticJsStrings::LENGTH,
785            PropertyDescriptor::builder()
786                .value(l)
787                .writable(false)
788                .enumerable(false)
789                .configurable(true),
790            context,
791        )
792        .js_expect("defining the `length` property for a new object should not fail")?;
793
794        // 8. Let targetName be ? Get(Target, "name").
795        let target_name = target.get(js_string!("name"), context)?;
796
797        // 9. If Type(targetName) is not String, set targetName to the empty String.
798        let target_name = target_name.as_string().unwrap_or_default();
799
800        // 10. Perform SetFunctionName(F, targetName, "bound").
801        set_function_name(&f, &target_name.into(), Some(js_str!("bound")), context)?;
802
803        // 11. Return F.
804        Ok(f.into())
805    }
806
807    /// `Function.prototype.call ( thisArg, ...args )`
808    ///
809    /// The `call()` method calls a function with a given this value and arguments provided individually.
810    ///
811    /// More information:
812    ///  - [MDN documentation][mdn]
813    ///  - [ECMAScript reference][spec]
814    ///
815    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call
816    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
817    fn call(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
818        // 1. Let func be the this value.
819        // 2. If IsCallable(func) is false, throw a TypeError exception.
820        let func = this.as_callable().ok_or_else(|| {
821            JsNativeError::typ().with_message(format!("{} is not a function", this.display()))
822        })?;
823        let this_arg = args.get_or_undefined(0);
824
825        // 3. Perform PrepareForTailCall().
826        // TODO?: 3. Perform PrepareForTailCall
827
828        // 4. Return ? Call(func, thisArg, args).
829        func.call(this_arg, args.get(1..).unwrap_or(&[]), context)
830    }
831
832    /// `Function.prototype.toString()`
833    ///
834    /// More information:
835    ///  - [MDN documentation][mdn]
836    ///  - [ECMAScript reference][spec]
837    ///
838    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype.tostring
839    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString
840    #[allow(clippy::wrong_self_convention)]
841    fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
842        // 1. Let func be the this value.
843        let func = this;
844
845        // TODO:
846        //    2. If func is an Object, func has a [[SourceText]] internal slot, func.[[SourceText]] is a sequence of Unicode code points,and HostHasSourceTextAvailable(func) is true, then
847        //        a. Return CodePointsToString(func.[[SourceText]]).
848
849        // 3. If func is a built-in function object, return an implementation-defined String source code representation of func.
850        //    The representation must have the syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal slot and
851        //    func.[[InitialName]] is a String, the portion of the returned String that would be matched by
852        //    NativeFunctionAccessor_opt PropertyName must be the value of func.[[InitialName]].
853
854        // 4. If func is an Object and IsCallable(func) is true, return an implementation-defined String source code representation of func.
855        //    The representation must have the syntax of a NativeFunction.
856        // 5. Throw a TypeError exception.
857        let Some(object) = func.as_callable() else {
858            return Err(JsNativeError::typ().with_message("not a function").into());
859        };
860
861        if object.is::<NativeFunctionObject>() {
862            let name = {
863                // Is there a case here where if there is no name field on a value
864                // name should default to None? Do all functions have names set?
865                let value = object.get(js_string!("name"), &mut *context)?;
866                if value.is_null_or_undefined() {
867                    js_string!()
868                } else {
869                    value.to_string(context)?
870                }
871            };
872            return Ok(
873                js_string!(js_str!("function "), &name, js_str!("() { [native code] }")).into(),
874            );
875        } else if object.is::<Proxy>() || object.is::<BoundFunction>() {
876            return Ok(js_string!("function () { [native code] }").into());
877        }
878
879        let function = object
880            .downcast_ref::<OrdinaryFunction>()
881            .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?;
882
883        let code = function.codeblock();
884        if let Some(code_points) = code.source_info().text_spanned().to_code_points() {
885            return Ok(JsString::from(code_points).into());
886        }
887
888        Ok(js_string!(
889            js_str!("function "),
890            code.name(),
891            js_str!("() { [native code] }")
892        )
893        .into())
894    }
895
896    /// `Function.prototype [ @@hasInstance ] ( V )`
897    ///
898    /// More information:
899    ///  - [ECMAScript reference][spec]
900    ///
901    /// [spec]: https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance
902    fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
903        // 1. Let F be the this value.
904        // 2. Return ? OrdinaryHasInstance(F, V).
905        Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into())
906    }
907
908    #[allow(clippy::unnecessary_wraps)]
909    fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
910        Ok(JsValue::undefined())
911    }
912}
913
914/// Abstract operation `SetFunctionName`
915///
916/// More information:
917///  - [ECMAScript reference][spec]
918///
919/// [spec]: https://tc39.es/ecma262/#sec-setfunctionname
920pub(crate) fn set_function_name(
921    function: &JsObject,
922    name: &PropertyKey,
923    prefix: Option<JsStr<'_>>,
924    context: &mut Context,
925) -> JsResult<()> {
926    // 1. Assert: F is an extensible object that does not have a "name" own property.
927    // 2. If Type(name) is Symbol, then
928    let mut name = match name {
929        PropertyKey::Symbol(sym) => {
930            // a. Let description be name's [[Description]] value.
931            // b. If description is undefined, set name to the empty String.
932            // c. Else, set name to the string-concatenation of "[", description, and "]".
933            sym.description().map_or_else(
934                || js_string!(),
935                |desc| js_string!(js_str!("["), &desc, js_str!("]")),
936            )
937        }
938        PropertyKey::String(string) => string.clone(),
939        PropertyKey::Index(index) => js_string!(format!("{}", index.get())),
940    };
941
942    // 3. Else if name is a Private Name, then
943    // a. Set name to name.[[Description]].
944    // todo: implement Private Names
945
946    // 4. If F has an [[InitialName]] internal slot, then
947    // a. Set F.[[InitialName]] to name.
948    // todo: implement [[InitialName]] for builtins
949
950    // 5. If prefix is present, then
951    if let Some(prefix) = prefix {
952        name = js_string!(prefix, js_str!(" "), &name);
953        // b. If F has an [[InitialName]] internal slot, then
954        // i. Optionally, set F.[[InitialName]] to name.
955        // todo: implement [[InitialName]] for builtins
956    }
957
958    // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name,
959    // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }).
960    function
961        .define_property_or_throw(
962            js_string!("name"),
963            PropertyDescriptor::builder()
964                .value(name)
965                .writable(false)
966                .enumerable(false)
967                .configurable(true),
968            context,
969        )
970        .js_expect("defining the `name` property must not fail per the spec")?;
971
972    Ok(())
973}
974
975/// Call this object.
976///
977/// # Panics
978///
979/// Panics if the object is currently mutably borrowed.
980// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
981// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
982pub(crate) fn function_call(
983    function_object: &JsObject,
984    argument_count: usize,
985    #[allow(
986        unused_variables,
987        reason = "Only used if native-backtrace feature is enabled"
988    )]
989    context: &mut InternalMethodCallContext<'_>,
990) -> JsResult<CallValue> {
991    context.check_runtime_limits()?;
992
993    let function = function_object
994        .downcast_ref::<OrdinaryFunction>()
995        .js_expect("not a function")?;
996    let realm = function.realm().clone();
997
998    if function.code.is_class_constructor() {
999        debug_assert!(
1000            function.is_ordinary(),
1001            "only ordinary functions can be classes"
1002        );
1003        return Err(JsNativeError::typ()
1004            .with_message("class constructor cannot be invoked without 'new'")
1005            .with_realm(realm)
1006            .into());
1007    }
1008
1009    let code = function.code.clone();
1010    let environments = function.environments.clone();
1011    let script_or_module = function.script_or_module.clone();
1012
1013    drop(function);
1014
1015    let env_fp = environments.len() as u32;
1016
1017    let frame = CallFrame::new(code, script_or_module, environments, realm)
1018        .with_argument_count(argument_count as u32)
1019        .with_env_fp(env_fp);
1020
1021    #[cfg(feature = "native-backtrace")]
1022    {
1023        let native_source_info = context.native_source_info();
1024        context
1025            .vm
1026            .shadow_stack
1027            .patch_last_native(native_source_info);
1028    }
1029
1030    context.vm.push_frame(frame);
1031    context.vm.set_return_value(JsValue::undefined());
1032
1033    let context = context.context();
1034
1035    let lexical_this_mode = context.vm.frame().code_block.this_mode == ThisMode::Lexical;
1036    let this = if lexical_this_mode {
1037        ThisBindingStatus::Lexical
1038    } else {
1039        let this = context.vm.stack.get_this(context.vm.frame());
1040        if context.vm.frame().code_block.strict() {
1041            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1042            ThisBindingStatus::Initialized(this)
1043        } else if this.is_null_or_undefined() {
1044            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1045            let this: JsValue = context.realm().global_this().clone().into();
1046            context.vm.stack.set_this(
1047                context.vm.frames.last().js_expect("frame must exist")?,
1048                this.clone(),
1049            );
1050            ThisBindingStatus::Initialized(this)
1051        } else {
1052            let this: JsValue = this
1053                .to_object(context)
1054                .js_expect("conversion cannot fail")?
1055                .into();
1056            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1057            context.vm.stack.set_this(
1058                context.vm.frames.last().js_expect("frame must exist")?,
1059                this.clone(),
1060            );
1061            ThisBindingStatus::Initialized(this)
1062        }
1063    };
1064
1065    let mut last_env = 0;
1066
1067    let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier();
1068    let has_function_scope = context.vm.frame().code_block().has_function_scope();
1069
1070    if has_binding_identifier {
1071        let frame = context.vm.frame_mut();
1072        let global = frame.realm.environment();
1073        let index = frame.environments.push_lexical(1, global);
1074        frame.environments.put_lexical_value(
1075            BindingLocatorScope::Stack(index),
1076            0,
1077            function_object.clone().into(),
1078            global,
1079        );
1080        last_env += 1;
1081    }
1082
1083    if has_function_scope {
1084        let scope = context.vm.frame().code_block().constant_scope(last_env);
1085        let frame = context.vm.frame_mut();
1086        let global = frame.realm.environment();
1087        frame.environments.push_function(
1088            scope,
1089            FunctionSlots::new(this, function_object.clone(), None),
1090            global,
1091        );
1092    }
1093
1094    Ok(CallValue::Ready)
1095}
1096
1097/// Construct an instance of this object with the specified arguments.
1098///
1099/// # Panics
1100///
1101/// Panics if the object is currently mutably borrowed.
1102// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
1103fn function_construct(
1104    this_function_object: &JsObject,
1105    argument_count: usize,
1106    context: &mut InternalMethodCallContext<'_>,
1107) -> JsResult<CallValue> {
1108    context.check_runtime_limits()?;
1109
1110    let function = this_function_object
1111        .downcast_ref::<OrdinaryFunction>()
1112        .js_expect("not a function")?;
1113    let realm = function.realm().clone();
1114
1115    debug_assert!(
1116        function.is_ordinary(),
1117        "only ordinary functions can be constructed"
1118    );
1119
1120    let code = function.code.clone();
1121    let environments = function.environments.clone();
1122    let script_or_module = function.script_or_module.clone();
1123    drop(function);
1124
1125    let env_fp = environments.len() as u32;
1126
1127    let new_target = context.vm.stack.pop();
1128
1129    let this = if code.is_derived_constructor() {
1130        None
1131    } else {
1132        // If the prototype of the constructor is not an object, then use the default object
1133        // prototype as prototype for the new object
1134        // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
1135        // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
1136        let prototype =
1137            get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?;
1138        let this = JsObject::from_proto_and_data_with_shared_shape(
1139            context.root_shape(),
1140            prototype,
1141            OrdinaryObject,
1142        )
1143        .upcast();
1144
1145        this.initialize_instance_elements(this_function_object, context)?;
1146
1147        Some(this)
1148    };
1149
1150    let mut frame = CallFrame::new(code, script_or_module, environments, realm)
1151        .with_argument_count(argument_count as u32)
1152        .with_env_fp(env_fp)
1153        .with_flags(CallFrameFlags::CONSTRUCT);
1154
1155    // We push the `this` below so we can mark this function as having the this value
1156    // cached if it's initialized.
1157    frame
1158        .flags
1159        .set(CallFrameFlags::THIS_VALUE_CACHED, this.is_some());
1160
1161    #[cfg(feature = "native-backtrace")]
1162    {
1163        let native_source_info = context.native_source_info();
1164        context
1165            .vm
1166            .shadow_stack
1167            .patch_last_native(native_source_info);
1168    }
1169
1170    context.vm.push_frame(frame);
1171    context.vm.set_return_value(JsValue::undefined());
1172
1173    let mut last_env = 0;
1174
1175    let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier();
1176    let has_function_scope = context.vm.frame().code_block().has_function_scope();
1177
1178    if has_binding_identifier {
1179        let frame = context.vm.frame_mut();
1180        let global = frame.realm.environment();
1181        let index = frame.environments.push_lexical(1, global);
1182        frame.environments.put_lexical_value(
1183            BindingLocatorScope::Stack(index),
1184            0,
1185            this_function_object.clone().into(),
1186            global,
1187        );
1188        last_env += 1;
1189    }
1190
1191    if has_function_scope {
1192        let scope = context.vm.frame().code_block().constant_scope(last_env);
1193        let frame = context.vm.frame_mut();
1194        let global = frame.realm.environment();
1195        frame.environments.push_function(
1196            scope,
1197            FunctionSlots::new(
1198                this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
1199                    ThisBindingStatus::Initialized(o.into())
1200                }),
1201                this_function_object.clone(),
1202                Some(
1203                    new_target
1204                        .as_object()
1205                        .js_expect("new.target should be an object")?
1206                        .clone(),
1207                ),
1208            ),
1209            global,
1210        );
1211    }
1212
1213    let context = context.context();
1214    context.vm.stack.set_this(
1215        context.vm.frames.last().js_expect("frame must exist")?,
1216        this.map(JsValue::new).unwrap_or_default(),
1217    );
1218
1219    Ok(CallValue::Ready)
1220}